Skip to content
Snippets Groups Projects
Unverified Commit 755e3e2c authored by Matt Layher's avatar Matt Layher Committed by GitHub
Browse files

Merge pull request #27 from chrigl/v2-vrf

Implemented VRF
parents 3703f882 00dc1cf3
No related branches found
No related tags found
No related merge requests found
......@@ -75,7 +75,7 @@ func NewClient(addr string, token string, client *http.Client) (*Client, error)
}
c.DCIM = NewDCIMService(c)
c.IPAM = &IPAMService{c: c}
c.IPAM = NewIPAMService(c)
c.Tenancy = NewTenancyService(c)
return c, nil
......
......@@ -306,3 +306,43 @@ func testTenantWithGroup(n int, t *TenantGroup) *Tenant {
Group: t,
}
}
func testTenantIdentifier(n int) *Tenant {
return &Tenant{
ID: n,
Name: fmt.Sprintf("Tenant %d", n),
Slug: fmt.Sprintf("tenant-%d", n),
}
}
func testVRF(n int) *VRF {
return &VRF{
ID: n,
Name: fmt.Sprintf("VRF %d", n),
RD: fmt.Sprintf("vrf-%d", n),
EnforceUnique: true,
Description: fmt.Sprintf("VRF %d Description", n),
Tenant: testTenantWithGroup(n, nil),
}
}
func testVRFCreate(n int) *VRF {
return &VRF{
Name: fmt.Sprintf("VRF %d", n),
RD: fmt.Sprintf("vrf-%d", n),
EnforceUnique: true,
Description: fmt.Sprintf("VRF %d Description", n),
Tenant: testTenantWithGroup(n, nil),
}
}
func testVRFWithTenant(n int, tenant *Tenant) *VRF {
return &VRF{
ID: n,
Name: fmt.Sprintf("VRF %d", n),
RD: fmt.Sprintf("vrf-%d", n),
EnforceUnique: true,
Description: fmt.Sprintf("VRF %d Description", n),
Tenant: tenant,
}
}
......@@ -16,5 +16,16 @@ package netbox
// An IPAMService is used in a Client to access NetBox's IPAM API methods.
type IPAMService struct {
c *Client
c *Client
VRFs *VRFsService
}
// NewIPAMService returns a IPAMService initialized with all sub-services.
func NewIPAMService(client *Client) *IPAMService {
return &IPAMService{
c: client,
VRFs: &VRFsService{
c: client,
},
}
}
// Copyright 2017 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.
// Code generated by generate_functions.go. DO NOT EDIT.
package netbox
import (
"encoding/json"
"fmt"
"net/http"
)
// VRFsService is used in a Client to access NetBox's ipam/vrfs API methods.
type VRFsService struct {
c *Client
}
// Get retrieves an VRF object from NetBox by its ID.
func (s *VRFsService) Get(id int) (*VRF, error) {
req, err := s.c.NewRequest(
http.MethodGet,
fmt.Sprintf("api/ipam/vrfs/%d/", id),
nil,
)
if err != nil {
return nil, err
}
t := new(VRF)
err = s.c.Do(req, t)
if err != nil {
return nil, err
}
return t, nil
}
// List returns a Page associated with an NetBox API Endpoint.
func (s *VRFsService) List(options *ListVRFOptions) *Page {
return NewPage(s.c, "api/ipam/vrfs/", options)
}
// Extract retrives a list of VRF objects from page.
func (s *VRFsService) Extract(page *Page) ([]*VRF, error) {
if err := page.Err(); err != nil {
return nil, err
}
var groups []*VRF
if err := json.Unmarshal(page.data.Results, &groups); err != nil {
return nil, err
}
return groups, nil
}
// Create creates a new VRF object in NetBox and returns the ID of the new object.
func (s *VRFsService) Create(data *VRF) (int, error) {
req, err := s.c.NewJSONRequest(http.MethodPost, "api/ipam/vrfs/", nil, data)
if err != nil {
return 0, err
}
g := new(writableVRF)
err = s.c.Do(req, g)
if err != nil {
return 0, err
}
return g.ID, nil
}
// Update changes an existing VRF object in NetBox, and returns the ID of the new object.
func (s *VRFsService) Update(data *VRF) (int, error) {
req, err := s.c.NewJSONRequest(
http.MethodPatch,
fmt.Sprintf("api/ipam/vrfs/%d/", data.ID),
nil,
data,
)
if err != nil {
return 0, err
}
// g is just used to verify correct api result.
// data is not changed, because the g is not the full representation that one would
// get with Get. But if the response was unmarshaled into writableVRF correctly,
// everything went fine, and we do not need to update data.
g := new(writableVRF)
err = s.c.Do(req, g)
if err != nil {
return 0, err
}
return g.ID, nil
}
// Delete deletes an existing VRF object from NetBox.
func (s *VRFsService) Delete(data *VRF) error {
req, err := s.c.NewRequest(
http.MethodDelete,
fmt.Sprintf("api/ipam/vrfs/%d/", data.ID),
nil,
)
if err != nil {
return err
}
return s.c.Do(req, nil)
}
// Copyright 2017 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.
// Code generated by generate_basic_tests.go. DO NOT EDIT.
package netbox
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"reflect"
"testing"
)
// Using this to override MarshalJSON
// In all cases when posting data to netbox-API, the VRF.MarshalJSON is what you want,
// but not here as a return in testHandler
type serverDataVRF VRF
func convertToServerDataVRF(data []*VRF) []*serverDataVRF {
dataWant := make([]*serverDataVRF, len(data))
for i := range data {
tmp := serverDataVRF(*data[i])
dataWant[i] = &tmp
}
return dataWant
}
func TestBasicVRFGet(t *testing.T) {
var tests = []struct {
desc string
want *VRF
}{
{
desc: "Simple VRF",
want: testVRF(1),
},
}
for i, tt := range tests {
t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) {
serverData := serverDataVRF(*tt.want)
c, done := testClient(t, testHandler(t, http.MethodGet, "/api/ipam/vrfs/1/", &serverData))
defer done()
res, err := c.IPAM.VRFs.Get(1)
if err != nil {
t.Fatalf("unexpected error from Client.IPAM.VRFs.Get: %v", err)
}
if want, got := tt.want, res; !reflect.DeepEqual(want, got) {
t.Fatalf("unexpected VRF:\n- want: %v\n- got: %v", want, got)
}
})
}
}
func TestBasicVRFGet404(t *testing.T) {
c, done := testClient(t, testStatusHandler(t, http.MethodGet, "/api/ipam/vrfs/1/", &struct {
Detail string `json:"detail"`
}{
Detail: "Not found.",
},
http.StatusNotFound))
defer done()
res, err := c.IPAM.VRFs.Get(1)
errstr := "404 - Not found."
if want, got := errors.New(errstr), err; !reflect.DeepEqual(want, got) {
t.Fatalf("unexpected error from Client.IPAM.VRFs.Get:\n- want: %v\n- got: %v", want, got)
}
if res != nil {
t.Fatalf("unexpected result:\n- want: %v\n- got: %v", nil, res)
}
}
func TestBasicListExtractVRF(t *testing.T) {
want := []*VRF{
testVRF(1),
testVRF(2),
}
serverWant := convertToServerDataVRF(want)
serverData, _ := json.Marshal(serverWant)
c, done := testClient(t, testHandler(t, http.MethodGet, "/api/ipam/vrfs/", &pageData{
Count: 2,
NextURL: "",
PreviousURL: "",
Results: serverData,
}))
defer done()
page := c.IPAM.VRFs.List(nil)
if page == nil {
t.Fatalf("unexpexted result from c.IPAM.VRFs.List.")
}
got := []*VRF{}
counter := 0
for page.Next() {
var err error
got, err = c.IPAM.VRFs.Extract(page)
if err != nil {
t.Fatalf("unexpected error from c.IPAM.VRFs.Extract: %v", err)
}
counter = counter + 1
if counter > 2 { // Safe guard
break
}
}
if counter != 1 {
t.Fatalf("unexpected page count:\n- want: 1\n- got: %d", counter)
}
if !reflect.DeepEqual(want, got) {
t.Fatalf("unexpected result:\n- want: %v\n- got: %v", want, got)
}
if page.Err() != nil {
t.Fatalf("unexpected error from page:\n- want: %v\n- got: %v", want, got)
}
}
func TestBasicCreateVRF(t *testing.T) {
var tests = []struct {
desc string
data *VRF
want int
serverData interface{}
status int
errstr string
}{
{
desc: "Create with ID 0",
data: testVRFCreate(1),
want: 1,
status: 0,
errstr: "",
serverData: testVRF(1),
},
{
desc: "Create duplicate",
data: testVRFCreate(1),
want: 0,
status: http.StatusBadRequest,
errstr: "400 - {\"name\":[\"IpamVRFService with this name already exists.\"]}\n",
serverData: &struct {
Name []string `json:"name"`
}{
Name: []string{"IpamVRFService with this name already exists."},
},
},
}
for i, tt := range tests {
t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) {
c, done := testClient(t, testStatusHandler(t, http.MethodPost, "/api/ipam/vrfs/", tt.serverData, tt.status))
defer done()
var terr error
if tt.errstr != "" {
terr = errors.New(tt.errstr) // Using errstr and initialize real err here, to satisfy golint
}
res, err := c.IPAM.VRFs.Create(tt.data)
if want, got := terr, err; !reflect.DeepEqual(want, got) {
t.Fatalf("unexpected error:\n- want: %v\n- got: %v", want, got)
}
if want, got := tt.want, res; !reflect.DeepEqual(want, got) {
t.Fatalf("unexpected VRF:\n- want: %v\n- got: %v", want, got)
}
})
}
}
func TestBasicUpdateVRF(t *testing.T) {
var tests = []struct {
desc string
data *VRF
want int
serverData interface{}
status int
errstr string
}{
{
desc: "Update with ID 1",
data: testVRF(1),
want: 1,
serverData: testVRF(1),
status: 0,
errstr: "",
},
{
desc: "Update not found",
data: testVRF(1),
want: 0,
serverData: &struct {
Detail string
}{
Detail: "Not found.",
},
status: http.StatusNotFound,
errstr: "404 - Not found.",
},
{
desc: "Update to duplicate",
data: testVRF(1),
want: 0,
serverData: &struct {
Name []string `json:"name"`
}{
Name: []string{"IpamVRFService with this name already exists."},
},
status: http.StatusBadRequest,
errstr: "400 - {\"name\":[\"IpamVRFService with this name already exists.\"]}\n",
},
}
for i, tt := range tests {
t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) {
c, done := testClient(t, testStatusHandler(t, http.MethodPatch, "/api/ipam/vrfs/1/", tt.serverData, tt.status))
defer done()
var terr error
if tt.errstr != "" {
terr = errors.New(tt.errstr) // Using errstr and initialize real err here, to satisfy golint
}
res, err := c.IPAM.VRFs.Update(tt.data)
if want, got := terr, err; !reflect.DeepEqual(want, got) {
t.Fatalf("unexpected error:\n- want: %v\n- got: %v", want, got)
}
if want, got := tt.want, res; !reflect.DeepEqual(want, got) {
t.Fatalf("unexpected VRF:\n- want: %v\n- got: %v", want, got)
}
})
}
}
func TestBasicDeleteVRF(t *testing.T) {
var tests = []struct {
desc string
data *VRF
serverData interface{}
status int
errstr string
}{
{
desc: "Delete ID 1",
data: testVRF(1),
serverData: testVRF(1),
status: 0,
errstr: "",
},
{
desc: "Delete not Found",
data: testVRF(1),
serverData: &struct {
Detail string `json:"detail"`
}{
Detail: "Not found.",
},
status: http.StatusNotFound,
errstr: "404 - Not found.",
},
}
for i, tt := range tests {
t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) {
c, done := testClient(t, testStatusHandler(t, http.MethodDelete, "/api/ipam/vrfs/1/", tt.serverData, tt.status))
defer done()
var terr error
if tt.errstr != "" {
terr = errors.New(tt.errstr) // Using errstr and initialize real err here, to satisfy golint
}
err := c.IPAM.VRFs.Delete(tt.data)
if want, got := terr, err; !reflect.DeepEqual(want, got) {
t.Fatalf("unexpected error:\n- want: %v\n- got: %v", want, got)
}
})
}
}
// Copyright 2017 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 (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"reflect"
"testing"
)
func TestVRFGet(t *testing.T) {
var tests = []struct {
desc string
want *VRF
}{
{
desc: "Without Tenant",
want: testVRFWithTenant(1, nil),
},
{
desc: "With Tenant",
want: testVRFWithTenant(1, testTenantWithGroup(1, nil)),
},
}
for i, tt := range tests {
t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) {
serverData := serverDataVRF(*tt.want)
c, done := testClient(t, testHandler(t, http.MethodGet, "/api/ipam/vrfs/1/", &serverData))
defer done()
res, err := c.IPAM.VRFs.Get(1)
if err != nil {
t.Fatalf("unexpected error from Client.IPAM.VRFs.Get: %v", err)
}
if want, got := tt.want, res; !reflect.DeepEqual(want, got) {
t.Fatalf("unexpected Tenant:\n- want: %v\n- got: %v", want, got)
}
})
}
}
func TestVRFUnmarshalJSON(t *testing.T) {
var tests = []struct {
desc string
data []byte
want *VRF
}{
{
desc: "Nil Group",
data: []byte(`{ "id": 1, "name": "VRF 1", "rd": "vrf-1", "tenant": null, "enforce_unique": true, "description": "VRF 1 Description", "custom_fields": {} }`),
want: testVRFWithTenant(1, nil),
},
{
desc: "With Group",
data: []byte(`{ "id": 1, "name": "VRF 1", "rd": "vrf-1", "tenant": { "id": 1, "name": "Tenant 1", "slug": "tenant-1" }, "enforce_unique": true, "description": "VRF 1 Description", "custom_fields": {} }`),
want: testVRFWithTenant(1, testTenantIdentifier(1)),
},
}
for i, tt := range tests {
t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) {
result := new(VRF)
err := json.Unmarshal(tt.data, result)
if err != nil {
t.Fatalf("unexpected error from Tenant.UnmarshalJSON: %v", err)
}
if want, got := tt.want, result; !reflect.DeepEqual(want, got) {
t.Fatalf("unexpected Tenant:\n- want: %v\n- got: %v", want, got)
}
})
}
}
func TestVRFMarshalJSON(t *testing.T) {
var tests = []struct {
desc string
data *VRF
want []byte
}{
{
desc: "Nil Group",
data: testVRFWithTenant(1, nil),
want: []byte(`{"id":1,"name":"VRF 1","rd":"vrf-1","enforce_unique":true,"description":"VRF 1 Description"}`),
},
{
desc: "With Group",
data: testVRFWithTenant(1, testTenantIdentifier(1)),
want: []byte(`{"id":1,"name":"VRF 1","rd":"vrf-1","enforce_unique":true,"description":"VRF 1 Description","tenant":1}`),
},
{
desc: "No VRF.ID",
data: testVRFWithTenant(0, nil),
want: []byte(`{"name":"VRF 0","rd":"vrf-0","enforce_unique":true,"description":"VRF 0 Description"}`),
},
}
for i, tt := range tests {
t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) {
result, err := json.Marshal(tt.data)
if err != nil {
t.Fatalf("unexpected error from VRF.MarshalJSON: %v", err)
}
if want, got := tt.want, result; bytes.Compare(want, got) != 0 {
t.Fatalf("unexpected VRF:\n- want: %s\n- got: %s", want, got)
}
})
}
}
func TestListVRFOptions(t *testing.T) {
enforceUniqueTrue := true
enforceUniqueFalse := false
var tests = []struct {
desc string
o *ListVRFOptions
v url.Values
}{
{
desc: "empty options",
},
{
desc: "full options",
o: &ListVRFOptions{
Name: "Hello",
RD: "hello",
EnforceUnique: &enforceUniqueTrue,
IDIn: []uint64{1, 2, 3},
TenantID: 1,
Query: "World",
},
v: url.Values{
"name": []string{"Hello"},
"rd": []string{"hello"},
"id__in": []string{"1,2,3"},
"enforce_unique": []string{"True"},
"tenant_id": []string{"1"},
"q": []string{"World"},
},
},
{
desc: "tenant vs tenant_id",
o: &ListVRFOptions{
TenantID: 1,
Tenant: "Tenant 1",
},
v: url.Values{
"tenant_id": []string{"1"},
},
},
{
desc: "tenant name",
o: &ListVRFOptions{
Tenant: "Tenant 1",
},
v: url.Values{
"tenant": []string{"Tenant 1"},
},
},
{
desc: "enforce_unique default value",
o: &ListVRFOptions{},
v: url.Values{},
},
{
desc: "enforce_unique true",
o: &ListVRFOptions{
EnforceUnique: &enforceUniqueTrue,
},
v: url.Values{
"enforce_unique": []string{"True"},
},
},
{
desc: "enforce_unique false",
o: &ListVRFOptions{
EnforceUnique: &enforceUniqueFalse,
},
v: url.Values{
"enforce_unique": []string{"False"},
},
},
}
for i, tt := range tests {
t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) {
v, err := tt.o.Values()
if err != nil {
t.Fatalf("unexpected Values error: %v", err)
}
if want, got := tt.v, v; !reflect.DeepEqual(want, got) {
t.Fatalf("unexpected url.Values map:\n- want: %v\n- got: %v", want, got)
}
})
}
}
// Copyright 2017 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 (
"encoding/json"
"net/url"
"strconv"
"strings"
)
// A VRF is a representation of netbox vrf
type VRF struct {
ID int `json:"id,omitempty"`
Name string `json:"name"`
RD string `json:"rd"`
EnforceUnique bool `json:"enforce_unique"`
Description string `json:"description"`
Tenant *Tenant `json:"tenant,omitempty"`
}
// A writableVRF corresponds to the Netbox API's
// writable serializer for an VRF. It is used transparently
// when VRF are serialized in to JSON.
type writableVRF struct {
ID int `json:"id,omitempty"`
Name string `json:"name"`
RD string `json:"rd"`
EnforceUnique bool `json:"enforce_unique"`
Description string `json:"description"`
Tenant *int `json:"tenant,omitempty"`
}
// MarshalJSON marshals an VRF into JSON bytes,
// and is used by the standard json package.
func (v *VRF) MarshalJSON() ([]byte, error) {
var tenantID *int
if v.Tenant != nil {
tenantID = &v.Tenant.ID
}
return json.Marshal(writableVRF{
ID: v.ID,
Name: v.Name,
RD: v.RD,
EnforceUnique: v.EnforceUnique,
Description: v.Description,
Tenant: tenantID,
})
}
// ListVRFOptions is used as an argument for Client.IPAM.VRFs.List.
type ListVRFOptions struct {
Name string
RD string
EnforceUnique *bool
IDIn []uint64
TenantID int
Tenant string
Query string
}
// Values generates a url.Values map from the data in ListVRFOptions.
func (o *ListVRFOptions) Values() (url.Values, error) {
if o == nil {
return nil, nil
}
v := url.Values{}
if o.Name != "" {
v.Set("name", o.Name)
}
if o.RD != "" {
v.Set("rd", o.RD)
}
if o.EnforceUnique != nil {
if *o.EnforceUnique {
v.Set("enforce_unique", "True")
} else {
v.Set("enforce_unique", "False")
}
}
if len(o.IDIn) > 0 {
vals := make([]string, len(o.IDIn))
for i, k := range o.IDIn {
vals[i] = strconv.FormatUint(k, 10)
}
v.Set("id__in", strings.Join(vals, ","))
}
switch {
case o.TenantID != 0:
v.Set("tenant_id", strconv.Itoa(o.TenantID))
case o.Tenant != "":
v.Set("tenant", o.Tenant)
}
if o.Query != "" {
v.Set("q", o.Query)
}
return v, nil
}
//go:generate go run generate_functions.go -type-name VRF -update-type-name writableVRF -service-name VRFsService -endpoint ipam -service vrfs
//go:generate go run generate_basic_tests.go -type-name VRF -service-name IpamVRFService -endpoint ipam -service vrfs -client-endpoint IPAM -client-service VRFs
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment