From c42cbe79a0a57ee2528dc9ec80accabc1f9a5382 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 28 Oct 2014 16:55:33 -0700 Subject: [PATCH] Add the ability to create, delete, list, and get networks, ports and subnets Partially implements network-v2 Updated to use Session Change-Id: I9c57cbe524712313892868c0fb38eb6f6062d362 --- examples/40-network-v2.go | 252 +++++++++++++++++++++++++++++++++++++ network/v2/network.go | 94 ++++++++++++++ network/v2/network_test.go | 107 ++++++++++++++++ network/v2/port.go | 109 ++++++++++++++++ network/v2/port_test.go | 104 +++++++++++++++ network/v2/subnet.go | 106 ++++++++++++++++ network/v2/subnet_test.go | 107 ++++++++++++++++ testUtil/testUtil.go | 17 +++ 8 files changed, 896 insertions(+) create mode 100644 examples/40-network-v2.go create mode 100644 network/v2/network.go create mode 100644 network/v2/network_test.go create mode 100644 network/v2/port.go create mode 100644 network/v2/port_test.go create mode 100644 network/v2/subnet.go create mode 100644 network/v2/subnet_test.go diff --git a/examples/40-network-v2.go b/examples/40-network-v2.go new file mode 100644 index 0000000..7bd747b --- /dev/null +++ b/examples/40-network-v2.go @@ -0,0 +1,252 @@ +// Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +// +// 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 main + +import ( + "fmt" + "net/http" + "reflect" + "time" + + "git.openstack.org/openstack/golang-client.git/network/v2" + "git.openstack.org/openstack/golang-client.git/openstack" +) + +func main() { + config := getConfig() + + // Authenticate with a username, password, tenant id. + creds := openstack.AuthOpts{ + AuthUrl: config.Host, + Project: config.ProjectName, + Username: config.Username, + Password: config.Password, + } + auth, err := openstack.DoAuthRequest(creds) + if err != nil { + panicString := fmt.Sprint("There was an error authenticating:", err) + panic(panicString) + } + if !auth.GetExpiration().After(time.Now()) { + panic("There was an error. The auth token has an invalid expiration.") + } + + // Find the endpoint for the network service. + url, err := auth.GetEndpoint("network", "") + if url == "" || err != nil { + panic("v2 network service url not found during authentication") + } + + // Make a new client with these creds + sess, err := openstack.NewSession(nil, auth, nil) + if err != nil { + panicString := fmt.Sprint("Error crating new Session:", err) + panic(panicString) + } + + networkService := network.Service{ + Session: *sess, + Client: *http.DefaultClient, + URL: url + "/v2.0", // We're forcing Network v2.0 for now + } + + networkName := "OtherNetwork" + activeNetwork := CreateNewNetworkVerifyExistsAndActive(networkService, networkName) + + // Create list, get and delete a subnet + activeSubnet := CreateSubnetAndVerify(networkService, activeNetwork) + activePort := CreatePortAndVerify(networkService, activeSubnet, activeNetwork) + DeletePortAndVerify(networkService, activePort) + DeleteSubnetAndVerify(networkService, activeSubnet) + DeleteNetworkAndVerify(networkService, activeNetwork) +} + +func CreatePortAndVerify(networkService network.Service, subnet network.SubnetResponse, activeNetwork network.Response) network.PortResponse { + var portToCreate = network.CreatePortParameters{AdminStateUp: false, Name: "testPort", NetworkID: activeNetwork.ID} + + portCreated, err := networkService.CreatePort(portToCreate) + if err != nil { + panicString := fmt.Sprint("Error in creating port:", err) + panic(panicString) + } + + foundPorts, err := networkService.Ports() + if err != nil { + panicString := fmt.Sprint("Error in getting list of subnets:", err) + panic(panicString) + } + + foundCreatedPort := false + for _, portFound := range foundPorts { + if reflect.DeepEqual(portCreated, portFound) { + foundCreatedPort = true + } + } + + if !foundCreatedPort { + panic("Cannot find the newly created port.") + } + + return portCreated +} + +func DeletePortAndVerify(networkService network.Service, port network.PortResponse) { + err := networkService.DeletePort(port.ID) + if err != nil { + panicString := fmt.Sprint("Error in deleting port:", err) + panic(panicString) + } + + ports, err := networkService.Ports() + if err != nil { + panicString := fmt.Sprint("There was an error getting a list of ports to verify the port was deleted:", err) + panic(panicString) + } + + portDeleted := true + for _, portFound := range ports { + if reflect.DeepEqual(portFound, port) { + portDeleted = false + } + } + + if !portDeleted { + panic("port was not deleted.") + } +} + +func CreateSubnetAndVerify(networkService network.Service, activeNetwork network.Response) network.SubnetResponse { + var allocationPools = []network.AllocationPool{network.AllocationPool{Start: "10.1.2.5", End: "10.1.2.15"}} + subnetToCreate := network.CreateSubnetParameters{NetworkID: activeNetwork.ID, IPVersion: network.IPV4, + CIDR: "10.1.2.1/25", AllocationPools: allocationPools} + + subnetCreated, err := networkService.CreateSubnet(subnetToCreate) + if err != nil { + panicString := fmt.Sprint("Error in creating subnet:", err) + panic(panicString) + } + + foundSubnets, err := networkService.Subnets() + if err != nil { + panicString := fmt.Sprint("Error in getting list of subnets:", err) + panic(panicString) + } + + foundCreatedSubnet := false + for _, subnetFound := range foundSubnets { + if reflect.DeepEqual(subnetCreated, subnetFound) { + foundCreatedSubnet = true + } + } + + if !foundCreatedSubnet { + panic("Cannot find the newly created subnet.") + } + + return subnetCreated +} + +func DeleteSubnetAndVerify(networkService network.Service, subnet network.SubnetResponse) { + err := networkService.DeleteSubnet(subnet.ID) + if err != nil { + panicString := fmt.Sprint("Error in deleting subnet:", err) + panic(panicString) + } + + subnets, err := networkService.Subnets() + if err != nil { + panicString := fmt.Sprint("There was an error getting a list of networks to verify the network was deleted:", err) + panic(panicString) + } + + subnetDeleted := true + for _, subnetFound := range subnets { + if reflect.DeepEqual(subnetFound, subnet) { + subnetDeleted = false + } + } + + if !subnetDeleted { + panic("subnet was not deleted.") + } +} + +func DeleteNetworkAndVerify(networkService network.Service, activeNetwork network.Response) { + err := networkService.DeleteNetwork(activeNetwork.ID) + if err != nil { + panicString := fmt.Sprint("Error in deleting 'OtherNetwork'", err) + panic(panicString) + } + + networks, err := networkService.Networks() + if err != nil { + panicString := fmt.Sprint("There was an error getting a list of networks to verify the network was deleted:", err) + panic(panicString) + } + + networkDeleted := true + for _, networkFound := range networks { + if reflect.DeepEqual(activeNetwork, networkFound) { + networkDeleted = false + } + } + + if !networkDeleted { + panic("network was not deleted.") + } + +} +func CreateNewNetworkVerifyExistsAndActive(networkService network.Service, networkName string) network.Response { + createdNetwork, err := networkService.CreateNetwork(true, networkName, false) + if err != nil { + panicString := fmt.Sprint("There was an error creating a network:", err) + panic(panicString) + } + + networks, err := networkService.Networks() + if err != nil { + panicString := fmt.Sprint("There was an error getting a list of networks:", err) + panic(panicString) + } + + foundCreatedNetwork := false + for _, networkFound := range networks { + if reflect.DeepEqual(createdNetwork, networkFound) { + foundCreatedNetwork = true + } + } + + if !foundCreatedNetwork { + panic("Cannot find network called 'OtherNetwork' when getting a list of networks.") + } + + // Might be nice to have some sugar api that can do this easily for a developer... + // Keep iterating until active or until more than 5 tries has been exceeded. + //numTries := 0 + //activeNetwork := createdNetwork + //for activeNetwork.Status != "ACTIVE" && numTries < 5 { + // activeNetwork, _ = networkService.Network(createdNetwork.ID) + // numTries++ + // fmt.Println("Sleeping 50ms on try:" + string(numTries) + " with status currently " + activeNetwork.Status) + // sleepDuration, _ := time.ParseDuration("50ms") + // time.Sleep(sleepDuration) + //} + + //if activeNetwork.Status != "ACTIVE" { + // panic("The network is not in the active state and cannot be deleted") + //} + + return createdNetwork +} diff --git a/network/v2/network.go b/network/v2/network.go new file mode 100644 index 0000000..34b1018 --- /dev/null +++ b/network/v2/network.go @@ -0,0 +1,94 @@ +// Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +// +// 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 network is used to create, delete, and query, networks, ports and subnets +package network + +import ( + "net/http" + + "git.openstack.org/openstack/golang-client.git/openstack" +) + +// Service holds state that is use to make requests and get responses for networks, +// ports and subnets +type Service struct { + Client http.Client + Session openstack.Session + URL string +} + +// Response returns a set of values of the a network response. +type Response struct { + ID string `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + Subnets []string `json:"subnets"` + TenantID string `json:"tenant_id"` + RouterExternal bool `json:"router:external"` + AdminStateUp bool `json:"admin_state_up"` + Shared bool `json:"shared"` + PortSecurityEnabled bool `json:"port_security_enabled"` +} + +// Networks will issue a get query that returns a list of networks +func (networkService Service) Networks() ([]Response, error) { + reqURL := networkService.URL + "/networks" + nwContainer := networksResp{} + _, err := networkService.Session.GetJSON(reqURL, nil, nil, &nwContainer) + return nwContainer.Networks, err +} + +// Network will issue a get request for a specific network. +func (networkService Service) Network(id string) (Response, error) { + reqURL := networkService.URL + "/networks/" + id + nwContainer := networkResp{} + _, err := networkService.Session.GetJSON(reqURL, nil, nil, &nwContainer) + return nwContainer.Network, err +} + +// CreateNetwork will send a POST request to create a new network with the specified parameters. +func (networkService Service) CreateNetwork(adminStateUp bool, name string, shared bool) (Response, error) { + createParameters := createNetworkValuesContainer{createNetworkValues{Name: name, AdminStateUp: adminStateUp, Shared: shared, TenantID: ""}} + reqURL := networkService.URL + "/networks" + nwContainer := networkResp{} + _, err := networkService.Session.PostJSON(reqURL, nil, nil, &createParameters, &nwContainer) + return nwContainer.Network, err +} + +// DeleteNetwork will delete the specified network. +func (networkService Service) DeleteNetwork(name string) (err error) { + reqURL := networkService.URL + "/networks/" + name + _, err = networkService.Session.Delete(reqURL, nil, nil) + return err +} + +type createNetworkValues struct { + Name string `json:"name"` + AdminStateUp bool `json:"admin_state_up"` + Shared bool `json:"shared"` + TenantID string `json:"tenant_id"` +} + +type createNetworkValuesContainer struct { + Network createNetworkValues `json:"network"` +} + +type networksResp struct { + Networks []Response `json:"networks"` +} + +type networkResp struct { + Network Response `json:"network"` +} diff --git a/network/v2/network_test.go b/network/v2/network_test.go new file mode 100644 index 0000000..971ece5 --- /dev/null +++ b/network/v2/network_test.go @@ -0,0 +1,107 @@ +// Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +// +// 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 network_test + +import ( + "encoding/json" + "errors" + "git.openstack.org/openstack/golang-client.git/network/v2" + "git.openstack.org/openstack/golang-client.git/testutil" + "net/http" + "testing" +) + +var tokn = "eaaafd18-0fed-4b3a-81b4-663c99ec1cbb" +var subnets = []string{"10.3.5.2", "12.34.1.4"} +var sampleNetworkResponse = network.Response{ + ID: "16470140hb", + Name: "networkName", + Status: "active", + Subnets: subnets, + TenantID: "tenantID", + RouterExternal: true, + AdminStateUp: false, + Shared: true, + PortSecurityEnabled: false} + +func TestGetNetworks(t *testing.T) { + mockResponseObject := networksContainer{Networks: []network.Response{sampleNetworkResponse}} + apiServer := testUtil.CreateGetJSONTestRequestServerWithMockObject(t, tokn, mockResponseObject, "/networks") + defer apiServer.Close() + + networkService := CreateNetworkService(apiServer.URL) + networks, err := networkService.Networks() + if err != nil { + t.Error(err) + } + + if len(networks) != 1 { + t.Error(errors.New("Error: Expected 2 networks to be listed")) + } + testUtil.Equals(t, sampleNetworkResponse, networks[0]) +} + +func TestGetNetwork(t *testing.T) { + mockResponseObject := networkContainer{Network: sampleNetworkResponse} + apiServer := testUtil.CreateGetJSONTestRequestServerWithMockObject(t, tokn, mockResponseObject, "/networks/5270u2tg0") + defer apiServer.Close() + + networkService := CreateNetworkService(apiServer.URL) + network, err := networkService.Network("5270u2tg0") + if err != nil { + t.Error(err) + } + + testUtil.Equals(t, sampleNetworkResponse, network) +} + +func TestDeleteNetwork(t *testing.T) { + name := "networkName" + apiServer := testUtil.CreateDeleteTestRequestServer(t, tokn, "/networks/"+name) + defer apiServer.Close() + + networkService := CreateNetworkService(apiServer.URL) + err := networkService.DeleteNetwork(name) + if err != nil { + t.Error(err) + } +} + +func TestCreateNetwork(t *testing.T) { + mockResponse, _ := json.Marshal(networkContainer{sampleNetworkResponse}) + apiServer := testUtil.CreatePostJSONTestRequestServer(t, tokn, string(mockResponse), "/networks", + `{"network":{"name":"networkName","admin_state_up":false,"shared":true,"tenant_id":"tenantId"}}`) + defer apiServer.Close() + + networkService := CreateNetworkService(apiServer.URL) + actualNetwork, err := networkService.CreateNetwork(false, "networkName", true) + if err != nil { + t.Error(err) + } + + testUtil.Equals(t, sampleNetworkResponse, actualNetwork) +} + +func CreateNetworkService(url string) network.Service { + return network.Service{TokenID: tokn, TenantID: "tenantId", Client: *http.DefaultClient, URL: url} +} + +type networksContainer struct { + Networks []network.Response `json:"networks"` +} + +type networkContainer struct { + Network network.Response `json:"network"` +} diff --git a/network/v2/port.go b/network/v2/port.go new file mode 100644 index 0000000..c75ba00 --- /dev/null +++ b/network/v2/port.go @@ -0,0 +1,109 @@ +// Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +// +// 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 network + +import ( + // "git.openstack.org/openstack/golang-client.git/openstack" +) + +// PortResponse returns a set of values of the a port response. +type PortResponse struct { + ID string `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + AdminStateUp bool `json:"admin_state_up"` + PortSecurityEnabled bool `json:"port_security_enabled"` + DeviceID string `json:"device_id"` + DeviceOwner string `json:"device_owner"` + NetworkID string `json:"network_id"` + TenantID string `json:"tenant_id"` + MacAddress string `json:"mac_address"` + FixedIPs []FixedIP `json:"fixed_ips"` + SecurityGroups []string `json:"security_groups"` +} + +// CreatePortParameters holds a set of values that specify how +// to create a new port. +type CreatePortParameters struct { + AdminStateUp bool `json:"admin_state_up,omitempty"` + Name string `json:"name,omitempty"` + NetworkID string `json:"network_id"` + DeviceID string `json:"device_id,omitempty"` + MacAddress string `json:"mac_address,omitempty"` + FixedIPs []FixedIP `json:"fixed_ips,omitempty"` + SecurityGroups []string `json:"security_groups,omitempty"` +} + +// PortResponses is a type for a slice of PortResponses. +type PortResponses []PortResponse + +func (a PortResponses) Len() int { return len(a) } +func (a PortResponses) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a PortResponses) Less(i, j int) bool { return a[i].Name < a[j].Name } + +// FixedIP is holds data that specifies a fixed IP. +type FixedIP struct { + SubnetID string `json:"subnet_id"` + IPAddress string `json:"ip_address,omitempty"` +} + +// Ports issues a GET request that returns the found port responses +func (networkService Service) Ports() ([]PortResponse, error) { + reqURL := networkService.URL + "/ports" + var portResponse = portsResp{} + _, err := networkService.Session.GetJSON(reqURL, nil, nil, &portResponse) + if err != nil { + return nil, err + } + + return portResponse.Ports, nil +} + +// Port issues a GET request that returns a specific port response. +func (networkService Service) Port(id string) (PortResponse, error) { + reqURL := networkService.URL + "/ports/" + id + portResponse := portResp{} + _, err := networkService.Session.GetJSON(reqURL, nil, nil, &portResponse) + return portResponse.Port, err +} + +// DeletePort issues a DELETE to the specified port url to delete it. +func (networkService Service) DeletePort(id string) error { + reqURL := networkService.URL + "/ports/" + id + _, err := networkService.Session.Delete(reqURL, nil, nil) + return err +} + +// CreatePort issues a POST to create the specified port and return a PortResponse. +func (networkService Service) CreatePort(parameters CreatePortParameters) (PortResponse, error) { + reqURL := networkService.URL + "/ports" + parametersContainer := createPortContainer{Port: parameters} + portResponse := portResp{} + + _, err := networkService.Session.PostJSON(reqURL, nil, nil, ¶metersContainer, &portResponse) + return portResponse.Port, err +} + +type portsResp struct { + Ports []PortResponse `json:"ports"` +} + +type portResp struct { + Port PortResponse `json:"port"` +} + +type createPortContainer struct { + Port CreatePortParameters `json:"port"` +} diff --git a/network/v2/port_test.go b/network/v2/port_test.go new file mode 100644 index 0000000..b5770cd --- /dev/null +++ b/network/v2/port_test.go @@ -0,0 +1,104 @@ +// Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +// +// 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 network_test + +import ( + "encoding/json" + "errors" + "git.openstack.org/openstack/golang-client.git/network/v2" + "git.openstack.org/openstack/golang-client.git/testutil" + "testing" +) + +var fixedIps = []network.FixedIP{network.FixedIP{SubnetID: "125071206726"}, network.FixedIP{SubnetID: "15615526", IPAddress: "132.145.15.11"}} +var samplePortResponse = network.PortResponse{ + ID: "2490640zbg", + Name: "Port14", + Status: "active", + AdminStateUp: true, + PortSecurityEnabled: true, + DeviceID: "deviceid", + DeviceOwner: "deviceowner", + NetworkID: "networkid", + TenantID: "tenantid", + MacAddress: "macAddress", + FixedIPs: fixedIps, + SecurityGroups: []string{"sec1", "sec2"}} + +func TestGetPorts(t *testing.T) { + mockResponseObject := portsContainer{Ports: []network.PortResponse{samplePortResponse}} + apiServer := testUtil.CreateGetJSONTestRequestServerWithMockObject(t, tokn, mockResponseObject, "/ports") + defer apiServer.Close() + + networkService := CreateNetworkService(apiServer.URL) + ports, err := networkService.Ports() + if err != nil { + t.Error(err) + } + + if len(ports) != 1 { + t.Error(errors.New("Error: Expected 2 networks to be listed")) + } + testUtil.Equals(t, samplePortResponse, ports[0]) +} + +func TestGetPort(t *testing.T) { + mockResponseObject := portContainer{Port: samplePortResponse} + apiServer := testUtil.CreateGetJSONTestRequestServerWithMockObject(t, tokn, mockResponseObject, "/ports/23507256") + defer apiServer.Close() + + networkService := CreateNetworkService(apiServer.URL) + port, err := networkService.Port("23507256") + if err != nil { + t.Error(err) + } + testUtil.Equals(t, samplePortResponse, port) +} + +func TestDeletePort(t *testing.T) { + portName := "portName" + apiServer := testUtil.CreateDeleteTestRequestServer(t, tokn, "/ports/"+portName) + defer apiServer.Close() + + networkService := CreateNetworkService(apiServer.URL) + err := networkService.DeletePort(portName) + if err != nil { + t.Error(err) + } +} + +func TestCreatePort(t *testing.T) { + mockResponse, _ := json.Marshal(portContainer{Port: samplePortResponse}) + apiServer := testUtil.CreatePostJSONTestRequestServer(t, tokn, string(mockResponse), "/ports", + `{"port":{"admin_state_up":true,"name":"name","network_id":"networkid","fixed_ips":[{"subnet_id":"125071206726"},{"subnet_id":"15615526","ip_address":"132.145.15.11"}]}}`) + defer apiServer.Close() + + networkService := CreateNetworkService(apiServer.URL) + createPortParameters := network.CreatePortParameters{AdminStateUp: true, Name: "name", NetworkID: "networkid", FixedIPs: fixedIps} + actualPort, err := networkService.CreatePort(createPortParameters) + if err != nil { + t.Error(err) + } + + testUtil.Equals(t, samplePortResponse, actualPort) +} + +type portsContainer struct { + Ports []network.PortResponse `json:"ports"` +} + +type portContainer struct { + Port network.PortResponse `json:"port"` +} diff --git a/network/v2/subnet.go b/network/v2/subnet.go new file mode 100644 index 0000000..32ec202 --- /dev/null +++ b/network/v2/subnet.go @@ -0,0 +1,106 @@ +// Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +// +// 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 network + +import ( + // "git.openstack.org/openstack/golang-client.git/openstack" +) + +// SubnetResponse returns a set of values of the a Subnet response +type SubnetResponse struct { + ID string `json:"id"` + Name string `json:"name"` + NetworkID string `json:"network_id"` + TenantID string `json:"tenant_id"` + EnableDHCP bool `json:"enable_dhcp"` + DNSNameserver []string `json:"dns_nameservers"` + AllocationPools []AllocationPool `json:"allocation_pools"` + HostRoutes []string `json:"host_routes"` + IPVersion IPVersion `json:"ip_version"` + GatewayIP string `json:"gateway_ip"` + CIDR string `json:"cidr"` +} + +// CreateSubnetParameters is a set of values to create a new subnet. +type CreateSubnetParameters struct { + NetworkID string `json:"network_id"` + IPVersion IPVersion `json:"ip_version"` + CIDR string `json:"cidr"` + AllocationPools []AllocationPool `json:"allocation_pools"` +} + +// AllocationPool is a set of values for an allocation pool of ip addresses. +type AllocationPool struct { + Start string `json:"start"` + End string `json:"end"` +} + +// IPVersion type indicates whether an ip address is IPV4 or IPV6. +type IPVersion int + +const ( + // IPV4 indicates its an ip address version 4. + IPV4 IPVersion = 4 + // IPV6 indicates its an ip address version 6 + IPV6 IPVersion = 6 +) + +// Subnets issues a GET request to return all subnets. +func (networkService Service) Subnets() ([]SubnetResponse, error) { + reqURL := networkService.URL + "/subnets" + var subnetResponse = subnetsResp{} + _, err := networkService.Session.GetJSON(reqURL, nil, nil, &subnetResponse) + return subnetResponse.Subnets, err +} + +// Subnet issues a GET request to a specific url of a subnet and returns a subnet response. +func (networkService Service) Subnet(id string) (SubnetResponse, error) { + reqURL := networkService.URL + "/subnets/" + id + + subnetResponse := subnetResp{} + _, err := networkService.Session.GetJSON(reqURL, nil, nil, &subnetResponse) + return subnetResponse.Subnet, err +} + +// DeleteSubnet issues a DELETE request to remove the subnet. +func (networkService Service) DeleteSubnet(id string) error { + reqURL := networkService.URL + "/subnets/" + id + _, err := networkService.Session.Delete(reqURL, nil, nil) + return err +} + +// CreateSubnet issues a GET request to add a Subnet with the specified parameters +// and returns the Subnet created. +func (networkService Service) CreateSubnet(parameters CreateSubnetParameters) (SubnetResponse, error) { + + reqURL := networkService.URL + "/subnets" + parametersContainer := createSubnetContainer{Subnet: parameters} + subnetResponse := subnetResp{} + + _, err := networkService.Session.PostJSON(reqURL, nil, nil, ¶metersContainer, &subnetResponse) + return subnetResponse.Subnet, err +} + +type subnetResp struct { + Subnet SubnetResponse `json:"subnet"` +} + +type subnetsResp struct { + Subnets []SubnetResponse `json:"subnets"` +} + +type createSubnetContainer struct { + Subnet CreateSubnetParameters `json:"subnet"` +} diff --git a/network/v2/subnet_test.go b/network/v2/subnet_test.go new file mode 100644 index 0000000..fc4b3e5 --- /dev/null +++ b/network/v2/subnet_test.go @@ -0,0 +1,107 @@ +// Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +// +// 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 network_test + +import ( + "encoding/json" + "errors" + "git.openstack.org/openstack/golang-client.git/network/v2" + "git.openstack.org/openstack/golang-client.git/testutil" + "testing" +) + +var allocationPool1 = network.AllocationPool{Start: "13.14.14.15", End: "13.14.14.15"} +var allocationPools = []network.AllocationPool{allocationPool1} +var sampleSubNetResponse = network.SubnetResponse{ + ID: "subnet", + Name: "name", + NetworkID: "networkid", + TenantID: "tenantid", + EnableDHCP: true, + DNSNameserver: []string{"13.14.14.15"}, + AllocationPools: allocationPools, + HostRoutes: []string{"35.15.15.15"}, + IPVersion: network.IPV4, + GatewayIP: "35.15.15.1", + CIDR: "35.15.15.3"} + +func TestGetSubnets(t *testing.T) { + mockResponseObject := subnetsResp{Subnets: []network.SubnetResponse{sampleSubNetResponse}} + apiServer := testUtil.CreateGetJSONTestRequestServerWithMockObject(t, tokn, mockResponseObject, "/subnets") + + defer apiServer.Close() + + networkService := CreateNetworkService(apiServer.URL) + subnets, err := networkService.Subnets() + if err != nil { + t.Error(err) + } + + if len(subnets) != 1 { + t.Error(errors.New("Error: Expected 1 subnet to be listed")) + } + testUtil.Equals(t, sampleSubNetResponse, subnets[0]) +} + +func TestGetSubnet(t *testing.T) { + mockResponseObject := subnetResp{Subnet: sampleSubNetResponse} + apiServer := testUtil.CreateGetJSONTestRequestServerWithMockObject(t, tokn, mockResponseObject, "/subnets/5270u2tg0") + defer apiServer.Close() + + networkService := CreateNetworkService(apiServer.URL) + subnet, err := networkService.Subnet("5270u2tg0") + if err != nil { + t.Error(err) + } + + testUtil.Equals(t, sampleSubNetResponse, subnet) +} + +func TestDeleteSubnet(t *testing.T) { + name := "subnetName" + apiServer := testUtil.CreateDeleteTestRequestServer(t, tokn, "/subnets/"+name) + defer apiServer.Close() + + networkService := CreateNetworkService(apiServer.URL) + err := networkService.DeleteSubnet(name) + if err != nil { + t.Error(err) + } +} + +func TestCreateSubnet(t *testing.T) { + mockResponse, err := json.Marshal(subnetResp{Subnet: sampleSubNetResponse}) + + apiServer := testUtil.CreatePostJSONTestRequestServer(t, tokn, string(mockResponse), "/subnets", + `{"subnet":{"network_id":"subnetid","ip_version":4,"cidr":"12.14.76.87","allocation_pools":[{"start":"13.14.14.15","end":"13.14.14.15"}]}}`) + defer apiServer.Close() + + networkService := CreateNetworkService(apiServer.URL) + createSubnetParameters := network.CreateSubnetParameters{NetworkID: "subnetid", AllocationPools: allocationPools, CIDR: "12.14.76.87", IPVersion: 4} + actualPort, err := networkService.CreateSubnet(createSubnetParameters) + if err != nil { + t.Error(err) + } + + testUtil.Equals(t, sampleSubNetResponse, actualPort) +} + +type subnetsResp struct { + Subnets []network.SubnetResponse `json:"subnets"` +} + +type subnetResp struct { + Subnet network.SubnetResponse `json:"subnet"` +} diff --git a/testUtil/testUtil.go b/testUtil/testUtil.go index ad48a53..4bacfcd 100644 --- a/testUtil/testUtil.go +++ b/testUtil/testUtil.go @@ -16,6 +16,7 @@ package testUtil import ( + "encoding/json" "errors" "fmt" "net/http" @@ -101,6 +102,22 @@ func CreateGetJSONTestRequestServer(t *testing.T, expectedAuthTokenValue string, })) } +// CreateGetJSONTestRequestServerWithMockObject a http.Server that can be used to test PostJson requests. Specify the token, +// response object which will be marshaled to a json payload and the expected ending url. +func CreateGetJSONTestRequestServerWithMockObject(t *testing.T, token string, mockResponseObject interface{}, urlEndsWith string) *httptest.Server { + mockResponse, err := json.Marshal(mockResponseObject) + if err != nil { + t.Error("Test failed to marshal mockResponseObject:", err) + } + anon := func(req *http.Request) { + reqURL := req.URL.String() + if !strings.HasSuffix(reqURL, urlEndsWith) { + t.Error(errors.New("Incorrect url created, expected:" + urlEndsWith + " at the end, actual url:" + reqURL)) + } + } + return CreateGetJSONTestRequestServer(t, token, string(mockResponse), anon) +} + // CreatePostJSONTestRequestServer creates a http.Server that can be used to test PostJson requests. Specify the token, // response json payload and the url and request body that is expected. func CreatePostJSONTestRequestServer(t *testing.T, expectedAuthTokenValue string, outputResponseJSONPayload string, expectedRequestURLEndsWith string, expectedRequestBody string) *httptest.Server {