diff --git a/acceptance/clients/clients.go b/acceptance/clients/clients.go index 8a43a4ee5..92645c863 100644 --- a/acceptance/clients/clients.go +++ b/acceptance/clients/clients.go @@ -333,6 +333,49 @@ func NewImageServiceV2Client() (*golangsdk.ServiceClient, error) { }) } +// NewNetworkV1Client returns a *ServiceClient for making calls to the +// OpenStack Networking v1 API. An error will be returned if authentication +// or client creation was not possible. +func NewNetworkV1Client() (*golangsdk.ServiceClient, error) { + ao, err := openstack.AuthOptionsFromEnv() + if err != nil { + return nil, err + } + + client, err := openstack.AuthenticatedClient(ao) + if err != nil { + return nil, err + } + + return openstack.NewNetworkV1(client, golangsdk.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) +} + +// NewPeerNetworkV1Client returns a *ServiceClient for making calls to the +// OpenStack Networking v1 API for VPC peer. An error will be returned if authentication +// or client creation was not possible. +func NewPeerNetworkV1Client() (*golangsdk.ServiceClient, error) { + ao, err := openstack.AuthOptionsFromEnv() + if err != nil { + return nil, err + } + + err = UpdatePeerTenantDetails(&ao) + if err != nil { + return nil, err + } + + client, err := openstack.AuthenticatedClient(ao) + if err != nil { + return nil, err + } + + return openstack.NewNetworkV1(client, golangsdk.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) +} + // NewNetworkV2Client returns a *ServiceClient for making calls to the // OpenStack Networking v2 API. An error will be returned if authentication // or client creation was not possible. @@ -352,6 +395,30 @@ func NewNetworkV2Client() (*golangsdk.ServiceClient, error) { }) } +// NewPeerNetworkV2Client returns a *ServiceClient for making calls to the +// OpenStack Networking v2 API for Peer. An error will be returned if authentication +// or client creation was not possible. +func NewPeerNetworkV2Client() (*golangsdk.ServiceClient, error) { + ao, err := openstack.AuthOptionsFromEnv() + if err != nil { + return nil, err + } + + err = UpdatePeerTenantDetails(&ao) + if err != nil { + return nil, err + } + + client, err := openstack.AuthenticatedClient(ao) + if err != nil { + return nil, err + } + + return openstack.NewNetworkV2(client, golangsdk.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) +} + // NewObjectStorageV1Client returns a *ServiceClient for making calls to the // OpenStack Object Storage v1 API. An error will be returned if authentication // or client creation was not possible. @@ -389,3 +456,20 @@ func NewSharedFileSystemV2Client() (*golangsdk.ServiceClient, error) { Region: os.Getenv("OS_REGION_NAME"), }) } + +func UpdatePeerTenantDetails(ao *golangsdk.AuthOptions) error { + + if peerTenantID := os.Getenv("OS_Peer_Tenant_ID"); peerTenantID != "" { + ao.TenantID = peerTenantID + ao.TenantName = "" + return nil + + } else if peerTenantName := os.Getenv("OS_Peer_Tenant_Name"); peerTenantName != "" { + ao.TenantID = "" + ao.TenantName = peerTenantName + return nil + + } else { + return fmt.Errorf("You're missing some important setup:\n OS_Peer_Tenant_ID or OS_Peer_Tenant_Name env variables must be provided.") + } +} diff --git a/acceptance/openstack/networking/v1/subnets/subnet.go b/acceptance/openstack/networking/v1/subnets/subnet.go new file mode 100644 index 000000000..754dee3e7 --- /dev/null +++ b/acceptance/openstack/networking/v1/subnets/subnet.go @@ -0,0 +1,86 @@ +package subnets + +import ( + "testing" + + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/acceptance/tools" + "github.com/huaweicloud/golangsdk/openstack/networking/v1/subnets" + "github.com/huaweicloud/golangsdk/openstack/networking/v1/vpcs" +) + +func CreateSubnetNResources(t *testing.T, client *golangsdk.ServiceClient) (*subnets.Subnet, error) { + + vpcName := tools.RandomString("TESTACC-", 8) + + createOpts := vpcs.CreateOpts{ + Name: vpcName, + CIDR: "192.168.20.0/24", + } + + t.Logf("Attempting to create vpc: %s", vpcName) + + vpc, err := vpcs.Create(client, createOpts).Extract() + if err != nil { + return nil, err + } + t.Logf("Created vpc: %s", vpcName) + + subnetName := tools.RandomString("ACPTTEST-", 8) + + createSubnetOpts := subnets.CreateOpts{ + Name: subnetName, + CIDR: "192.168.20.0/24", + GatewayIP: "192.168.20.1", + EnableDHCP: true, + AvailabilityZone: "eu-de-02", + VPC_ID: vpc.ID, + } + + t.Logf("Attempting to create subnet: %s", subnetName) + + subnet, err := subnets.Create(client, createSubnetOpts).Extract() + if err != nil { + return subnet, err + } + t.Logf("Created subnet: %v", subnet) + + return subnet, nil +} + +func DeleteSubnetNResources(t *testing.T, client *golangsdk.ServiceClient, vpcID string, id string) { + t.Logf("Attempting to delete subnet: %s", id) + + err := subnets.Delete(client, vpcID, id).ExtractErr() + if err != nil { + t.Fatalf("Error deleting subnet: %v", err) + } + + t.Logf("Deleted subnet: %s", id) + + if err := WaitForSubnetToDelete(client, id, 60); err != nil { + t.Fatalf("Error deleting subnet: %v", err) + } + + t.Logf("Attempting to delete vpc: %s", vpcID) + + err = vpcs.Delete(client, vpcID).ExtractErr() + if err != nil { + t.Fatalf("Error deleting vpc: %v", err) + } + + t.Logf("Deleted vpc: %s", vpcID) +} + +func WaitForSubnetToDelete(client *golangsdk.ServiceClient, subnetID string, secs int) error { + return golangsdk.WaitFor(secs, func() (bool, error) { + _, err := subnets.Get(client, subnetID).Extract() + if err != nil { + if _, ok := err.(golangsdk.ErrDefault404); ok { + return true, nil + } + } + + return false, nil + }) +} diff --git a/acceptance/openstack/networking/v1/subnets/subnet_test.go b/acceptance/openstack/networking/v1/subnets/subnet_test.go new file mode 100644 index 000000000..792e707ab --- /dev/null +++ b/acceptance/openstack/networking/v1/subnets/subnet_test.go @@ -0,0 +1,51 @@ +package subnets + +import ( + "testing" + + "github.com/huaweicloud/golangsdk/acceptance/clients" + "github.com/huaweicloud/golangsdk/acceptance/tools" + "github.com/huaweicloud/golangsdk/openstack/networking/v1/subnets" +) + +func TestSubnetList(t *testing.T) { + client, err := clients.NewNetworkV1Client() + if err != nil { + t.Fatalf("Unable to create a subnet : %v", err) + } + allPages, err := subnets.List(client, subnets.ListOpts{}) + tools.PrintResource(t, allPages) + +} + +func TestSubnetsCRUD(t *testing.T) { + client, err := clients.NewNetworkV1Client() + if err != nil { + t.Fatalf("Unable to create a subnet : %v", err) + } + + // Create a subnet + subnet, err := CreateSubnetNResources(t, client) + if err != nil { + t.Fatalf("Unable to create subnet: %v", err) + } + + // Delete a subnet + defer DeleteSubnetNResources(t, client, subnet.VPC_ID, subnet.ID) + tools.PrintResource(t, subnet) + + // Update a subnet + newName := tools.RandomString("ACPTTEST-", 8) + updateOpts := &subnets.UpdateOpts{ + Name: newName, + } + _, err = subnets.Update(client, subnet.VPC_ID, subnet.ID, updateOpts).Extract() + + // Query a subnet + newSubnet, err := subnets.Get(client, subnet.ID).Extract() + if err != nil { + t.Fatalf("Unable to retrieve subnet: %v", err) + } + + tools.PrintResource(t, newSubnet) +} diff --git a/acceptance/openstack/networking/v1/vpc.go b/acceptance/openstack/networking/v1/vpc.go new file mode 100644 index 000000000..a1255c372 --- /dev/null +++ b/acceptance/openstack/networking/v1/vpc.go @@ -0,0 +1,40 @@ +package v1 + +import ( + "testing" + + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/acceptance/tools" + "github.com/huaweicloud/golangsdk/openstack/networking/v1/vpcs" +) + +func CreateVpc(t *testing.T, client *golangsdk.ServiceClient) (*vpcs.Vpc, error) { + + vpcName := tools.RandomString("TESTACC-", 8) + + createOpts := vpcs.CreateOpts{ + Name: vpcName, + CIDR: "192.168.20.0/24", + } + + t.Logf("Attempting to create vpc: %s", vpcName) + + vpc, err := vpcs.Create(client, createOpts).Extract() + if err != nil { + return vpc, err + } + t.Logf("Created vpc: %s", vpcName) + + return vpc, nil +} + +func DeleteVpc(t *testing.T, client *golangsdk.ServiceClient, vpcID string) { + t.Logf("Attempting to delete vpc: %s", vpcID) + + err := vpcs.Delete(client, vpcID).ExtractErr() + if err != nil { + t.Fatalf("Error deleting vpc: %v", err) + } + + t.Logf("Deleted vpc: %s", vpcID) +} diff --git a/acceptance/openstack/networking/v1/vpcs_test.go b/acceptance/openstack/networking/v1/vpcs_test.go new file mode 100644 index 000000000..4114f9911 --- /dev/null +++ b/acceptance/openstack/networking/v1/vpcs_test.go @@ -0,0 +1,58 @@ +package v1 + +import ( + "testing" + + "github.com/huaweicloud/golangsdk/acceptance/clients" + "github.com/huaweicloud/golangsdk/acceptance/tools" + "github.com/huaweicloud/golangsdk/openstack/networking/v1/vpcs" +) + +func TestVpcList(t *testing.T) { + client, err := clients.NewNetworkV1Client() + if err != nil { + t.Fatalf("Unable to create a vpc client: %v", err) + } + + listOpts := vpcs.ListOpts{} + allVpcs, err := vpcs.List(client, listOpts) + if err != nil { + t.Fatalf("Unable to list vpcs: %v", err) + } + for _, vpc := range allVpcs { + tools.PrintResource(t, vpc) + } +} + +func TestVpcsCRUD(t *testing.T) { + client, err := clients.NewNetworkV1Client() + if err != nil { + t.Fatalf("Unable to create a vpc client: %v", err) + } + + // Create a vpc + vpc, err := CreateVpc(t, client) + if err != nil { + t.Fatalf("Unable to create create: %v", err) + } + defer DeleteVpc(t, client, vpc.ID) + + tools.PrintResource(t, vpc) + + newName := tools.RandomString("TESTACC-", 8) + updateOpts := &vpcs.UpdateOpts{ + Name: newName, + } + + _, err = vpcs.Update(client, vpc.ID, updateOpts).Extract() + if err != nil { + t.Fatalf("Unable to update vpc: %v", err) + } + + newVpc, err := vpcs.Get(client, vpc.ID).Extract() + if err != nil { + t.Fatalf("Unable to retrieve vpc: %v", err) + } + + tools.PrintResource(t, newVpc) +} diff --git a/acceptance/openstack/networking/v2/peering/peerings.go b/acceptance/openstack/networking/v2/peering/peerings.go new file mode 100644 index 000000000..adb08f754 --- /dev/null +++ b/acceptance/openstack/networking/v2/peering/peerings.go @@ -0,0 +1,133 @@ +package peering + +import ( + "testing" + + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/acceptance/clients" + "github.com/huaweicloud/golangsdk/acceptance/tools" + "github.com/huaweicloud/golangsdk/openstack/networking/v1/vpcs" + "github.com/huaweicloud/golangsdk/openstack/networking/v2/peerings" +) + +func CreatePeeringResourcesNConn(t *testing.T, clientV2 *golangsdk.ServiceClient, peerClientV2 *golangsdk.ServiceClient, + clientV1 *golangsdk.ServiceClient, peerClientV1 *golangsdk.ServiceClient) (*peerings.Peering, error) { + + vpcName := tools.RandomString("TESTACC-vpc", 8) + peerVpcName := tools.RandomString("TESTACC-peervpc", 8) + + createOpts := vpcs.CreateOpts{ + Name: vpcName, + CIDR: "192.168.20.0/24", + } + + t.Logf("Attempting to create vpc: %s and peer vpc: %s", vpcName, peerVpcName) + + vpc, err := vpcs.Create(clientV1, createOpts).Extract() + if err != nil { + return nil, err + } + + peerVpc, err := vpcs.Create(peerClientV1, createOpts).Extract() + if err != nil { + return nil, err + } + + t.Logf("Created vpcs: %s %s", vpcName, peerVpcName) + + peeringConnName := tools.RandomString("TESTACC-", 8) + + peerCreateOpts := peerings.CreateOpts{ + Name: peeringConnName, + RequestVpcInfo: peerings.VpcInfo{VpcId: vpc.ID}, + AcceptVpcInfo: peerings.VpcInfo{VpcId: peerVpc.ID, TenantId: peerClientV2.ProjectID}, + } + + t.Logf("Attempting to create vpc peering connection: %s", peeringConnName) + + peeringConns, err := peerings.Create(clientV2, peerCreateOpts).Extract() + if err != nil { + return peeringConns, err + } + + if err := WaitForPeeringConnToCreate(clientV2, peeringConns.ID, 60); err != nil { + return peeringConns, err + } + + t.Logf("Created vpc peering connection: %s", peeringConnName) + + return peeringConns, nil +} + +func DeletePeeringConnNResources(t *testing.T, clientV2 *golangsdk.ServiceClient, clientV1 *golangsdk.ServiceClient, + peerClientV1 *golangsdk.ServiceClient, peeringConn *peerings.Peering) { + t.Logf("Attempting to delete vpc peering connection: %s", peeringConn.ID) + + err := peerings.Delete(clientV2, peeringConn.ID).ExtractErr() + if err != nil { + t.Fatalf("Error deleting vpc peering connection: %v", err) + } + + t.Logf("Deleted vpc peering connection: %s", peeringConn.ID) + + t.Logf("Attempting to delete vpc: %s", peeringConn.RequestVpcInfo.VpcId) + + err = vpcs.Delete(clientV1, peeringConn.RequestVpcInfo.VpcId).ExtractErr() + if err != nil { + t.Fatalf("Error deleting vpc: %v", err) + } + + err = vpcs.Delete(peerClientV1, peeringConn.AcceptVpcInfo.VpcId).ExtractErr() + if err != nil { + t.Fatalf("Error deleting vpc: %v", err) + } + + t.Logf("Deleted vpcs: %s and %s", peeringConn.RequestVpcInfo.VpcId, peeringConn.AcceptVpcInfo.VpcId) +} + +func InitiatePeeringConnCommonTasks(t *testing.T) (*golangsdk.ServiceClient, *golangsdk.ServiceClient, + *golangsdk.ServiceClient, *golangsdk.ServiceClient, *peerings.Peering) { + + clientV2, err := clients.NewNetworkV2Client() + if err != nil { + t.Fatalf("Unable to create a network v2 client: %v", err) + } + + clientV1, err := clients.NewNetworkV1Client() + if err != nil { + t.Fatalf("Unable to create a network v1 client: %v", err) + } + + peerClientV2, err := clients.NewPeerNetworkV2Client() + if err != nil { + t.Fatalf("Unable to create a network v2 client for peer: %v", err) + } + + peerClientV1, err := clients.NewPeerNetworkV1Client() + if err != nil { + t.Fatalf("Unable to create a network v1 client for peer: %v", err) + } + + // Create a vpc peering connection + peeringConn, err := CreatePeeringResourcesNConn(t, clientV2, peerClientV2, clientV1, peerClientV1) + if err != nil { + t.Fatalf("Unable to create vpc peering connection: %v", err) + } + + return clientV2, peerClientV2, clientV1, peerClientV1, peeringConn +} + +func WaitForPeeringConnToCreate(client *golangsdk.ServiceClient, peeringConnID string, secs int) error { + return golangsdk.WaitFor(secs, func() (bool, error) { + conn, err := peerings.Get(client, peeringConnID).Extract() + if err != nil { + return false, err + } + + if conn.Status == "PENDING_ACCEPTANCE" { + return true, nil + } + + return false, nil + }) +} diff --git a/acceptance/openstack/networking/v2/peering/peerings_test.go b/acceptance/openstack/networking/v2/peering/peerings_test.go new file mode 100644 index 000000000..ffa0a3ac4 --- /dev/null +++ b/acceptance/openstack/networking/v2/peering/peerings_test.go @@ -0,0 +1,80 @@ +package peering + +import ( + "testing" + + "github.com/huaweicloud/golangsdk/acceptance/clients" + "github.com/huaweicloud/golangsdk/acceptance/tools" + "github.com/huaweicloud/golangsdk/openstack/networking/v2/peerings" +) + +func TestPeeringList(t *testing.T) { + client, err := clients.NewNetworkV2Client() + if err != nil { + t.Fatalf("Unable to create a vpc client: %v", err) + } + + listOpts := peerings.ListOpts{} + peering, err := peerings.List(client, listOpts) + if err != nil { + t.Fatalf("Unable to list peerings: %v", err) + } + for _, peering := range peering { + tools.PrintResource(t, peering) + } +} + +func TestAcceptPeering(t *testing.T) { + + clientV2, peerClientV2, clientV1, peerClientV1, peeringConn := InitiatePeeringConnCommonTasks(t) + + // Delete a vpc peering connection + defer DeletePeeringConnNResources(t, clientV2, clientV1, peerClientV1, peeringConn) + + peeringConn1, err := peerings.Accept(peerClientV2, peeringConn.ID).ExtractResult() + if err != nil { + t.Fatalf("Unable to accept peering request: %v", err) + } + tools.PrintResource(t, peeringConn1) + +} + +func TestRejectPeering(t *testing.T) { + + clientV2, peerClientV2, clientV1, peerClientV1, peeringConn := InitiatePeeringConnCommonTasks(t) + + // Delete a vpc peering connection + defer DeletePeeringConnNResources(t, clientV2, clientV1, peerClientV1, peeringConn) + + peerConn1, err := peerings.Reject(peerClientV2, peeringConn.ID).ExtractResult() + if err != nil { + t.Fatalf("Unable to Reject peering request: %v", err) + } + tools.PrintResource(t, peerConn1) + +} + +func TestPeeringCRUD(t *testing.T) { + + clientV2, peerClientV2, clientV1, peerClientV1, peeringConn := InitiatePeeringConnCommonTasks(t) + + // Delete a vpc peering connection + defer DeletePeeringConnNResources(t, clientV2, clientV1, peerClientV1, peeringConn) + + tools.PrintResource(t, peeringConn) + updateOpts := peerings.UpdateOpts{ + Name: "test2", + } + + _, err := peerings.Update(clientV2, peeringConn.ID, updateOpts).Extract() + if err != nil { + t.Fatalf("Unable to update vpc peering connection: %v", err) + } + + peeringConnGet, err := peerings.Get(peerClientV2, peeringConn.ID).Extract() + if err != nil { + t.Fatalf("Unable to retrieve vpc peering connection: %v", err) + } + + tools.PrintResource(t, peeringConnGet) +} diff --git a/acceptance/openstack/networking/v2/routes/route.go b/acceptance/openstack/networking/v2/routes/route.go new file mode 100644 index 000000000..1d40e0118 --- /dev/null +++ b/acceptance/openstack/networking/v2/routes/route.go @@ -0,0 +1,40 @@ +package routes + +import ( + "testing" + + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/openstack/networking/v2/peerings" + "github.com/huaweicloud/golangsdk/openstack/networking/v2/routes" +) + +func CreateRoute(t *testing.T, clientV2 *golangsdk.ServiceClient, peeringConn *peerings.Peering) (*routes.Route, error) { + + createRouteOpts := routes.CreateOpts{ + NextHop: peeringConn.ID, + Destination: "192.168.0.0/16", + VPC_ID: peeringConn.RequestVpcInfo.VpcId, + Type: "peering", + } + + t.Logf("Attempting to create route") + + route, err := routes.Create(clientV2, createRouteOpts).Extract() + if err != nil { + return route, err + } + t.Logf("Created route: %s", route) + + return route, nil +} + +func DeleteRoute(t *testing.T, clientV2 *golangsdk.ServiceClient, routeID string) { + t.Logf("Attempting to delete route: %s", routeID) + + err := routes.Delete(clientV2, routeID).ExtractErr() + if err != nil { + t.Fatalf("Error deleting route: %v", err) + } + + t.Logf("Deleted route: %s", routeID) +} diff --git a/acceptance/openstack/networking/v2/routes/route_test.go b/acceptance/openstack/networking/v2/routes/route_test.go new file mode 100644 index 000000000..83ba88d35 --- /dev/null +++ b/acceptance/openstack/networking/v2/routes/route_test.go @@ -0,0 +1,62 @@ +package routes + +import ( + "testing" + + "github.com/huaweicloud/golangsdk/acceptance/clients" + "github.com/huaweicloud/golangsdk/acceptance/openstack/networking/v2/peering" + "github.com/huaweicloud/golangsdk/acceptance/tools" + "github.com/huaweicloud/golangsdk/openstack/networking/v2/peerings" + "github.com/huaweicloud/golangsdk/openstack/networking/v2/routes" +) + +func TestRouteList(t *testing.T) { + client, err := clients.NewNetworkV2Client() + if err != nil { + t.Fatalf("Unable to create a route client: %v", err) + } + + listOpts := routes.ListOpts{} + pages, err := routes.List(client, listOpts).AllPages() + if err != nil { + t.Fatalf("Unable to list routers: %v", err) + } + + allRoutes, err := routes.ExtractRoutes(pages) + if err != nil { + t.Errorf("Failed to extract routes: %v", err) + } + + for _, router := range allRoutes { + tools.PrintResource(t, router) + } +} + +func TestRoutesCRUD(t *testing.T) { + + clientV2, peerClientV2, clientV1, peerClientV1, peeringConn := peering.InitiatePeeringConnCommonTasks(t) + + _, err := peerings.Accept(peerClientV2, peeringConn.ID).ExtractResult() + if err != nil { + t.Fatalf("Unable to accept peering request: %v", err) + } + + // Create a Route + route, err := CreateRoute(t, clientV2, peeringConn) + + if err != nil { + t.Fatalf("Unable to create route: %v", err) + } + + defer peering.DeletePeeringConnNResources(t, clientV2, clientV1, peerClientV1, peeringConn) + defer DeleteRoute(t, clientV2, route.RouteID) + + tools.PrintResource(t, route) + + newRoute, err := routes.Get(clientV2, route.RouteID).Extract() + if err != nil { + t.Fatalf("Unable to retrieve route: %v", err) + } + + tools.PrintResource(t, newRoute) +} diff --git a/openstack/networking/v1/common/common_tests.go b/openstack/networking/v1/common/common_tests.go new file mode 100644 index 000000000..bab798e5f --- /dev/null +++ b/openstack/networking/v1/common/common_tests.go @@ -0,0 +1,18 @@ +package common + +import ( + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/testhelper/client" +) + +const TokenID = client.TokenID + +// Fake project id to use. +const ProjectID = "85636478b0bd8e67e89469c7749d4127" + +func ServiceClient() *golangsdk.ServiceClient { + sc := client.ServiceClient() + sc.ResourceBase = sc.Endpoint + "v1/" + sc.ProjectID = ProjectID + return sc +} diff --git a/openstack/networking/v1/subnets/doc.go b/openstack/networking/v1/subnets/doc.go new file mode 100644 index 000000000..c418b7be2 --- /dev/null +++ b/openstack/networking/v1/subnets/doc.go @@ -0,0 +1,57 @@ +/* +Package Subnets enables management and retrieval of Subnets + +Example to List Vpcs + + listOpts := subnets.ListOpts{} + allSubnets, err := subnets.List(subnetClient, listOpts) + if err != nil { + panic(err) + } + + for _, subnet := range allSubnets { + fmt.Printf("%+v\n", subnet) + } + +Example to Create a Vpc + + createOpts := subnets.CreateOpts{ + Name: "test_subnets", + CIDR: "192.168.0.0/16" + GatewayIP: "192.168.0.1" + PRIMARY_DNS: "8.8.8.8" + SECONDARY_DNS: "8.8.4.4" + AvailabilityZone:"eu-de-02" + VPC_ID:"3b9740a0-b44d-48f0-84ee-42eb166e54f7" + + } + vpc, err := subnets.Create(subnetClient, createOpts).Extract() + + if err != nil { + panic(err) + } + +Example to Update a Vpc + + subnetID := "4e8e5957-649f-477b-9e5b-f1f75b21c03c" + + updateOpts := subnets.UpdateOpts{ + Name: "testsubnet", + } + + subnet, err := subnets.Update(subnetClient, subnetID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Vpc + + subnetID := "4e8e5957-649f-477b-9e5b-f1f75b21c03c" + + err := subnets.Delete(subnetClient, subnetID).ExtractErr() + + if err != nil { + panic(err) + } +*/ +package subnets diff --git a/openstack/networking/v1/subnets/requests.go b/openstack/networking/v1/subnets/requests.go new file mode 100644 index 000000000..893cf65f2 --- /dev/null +++ b/openstack/networking/v1/subnets/requests.go @@ -0,0 +1,218 @@ +package subnets + +import ( + "reflect" + + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/pagination" +) + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the floating IP attributes you want to see returned. SortKey allows you to +// sort by a particular network attribute. SortDir sets the direction, and is +// either `asc' or `desc'. Marker and Limit are used for pagination. + +type ListOpts struct { + // ID is the unique identifier for the subnet. + ID string `json:"id"` + + // Name is the human readable name for the subnet. It does not have to be + // unique. + Name string `json:"name"` + + //Specifies the network segment on which the subnet resides. + CIDR string `json:"cidr"` + + //Specifies the IP address list of DNS servers on the subnet. + DnsList []string `json:"dnsList"` + + // Status indicates whether or not a subnet is currently operational. + Status string `json:"status"` + + //Specifies the gateway of the subnet. + GatewayIP string `json:"gateway_ip"` + + //Specifies whether the DHCP function is enabled for the subnet. + EnableDHCP bool `json:"dhcp_enable"` + + //Specifies the IP address of DNS server 1 on the subnet. + PRIMARY_DNS string `json:"primary_dns"` + + //Specifies the IP address of DNS server 2 on the subnet. + SECONDARY_DNS string `json:"secondary_dns"` + + //Identifies the availability zone (AZ) to which the subnet belongs. + AvailabilityZone string `json:"availability_zone"` + + //Specifies the ID of the VPC to which the subnet belongs. + VPC_ID string `json:"vpc_id"` +} + +// List returns collection of +// subnets. It accepts a ListOpts struct, which allows you to filter and sort +// the returned collection for greater efficiency. +// +// Default policy settings return only those subnets that are owned by the +// tenant who submits the request, unless an admin user submits the request. + +func List(c *golangsdk.ServiceClient, opts ListOpts) ([]Subnet, error) { + u := rootURL(c) + pages, err := pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page { + return SubnetPage{pagination.LinkedPageBase{PageResult: r}} + }).AllPages() + + allSubnets, err := ExtractSubnets(pages) + if err != nil { + return nil, err + } + + return FilterSubnets(allSubnets, opts) +} + +func FilterSubnets(subnets []Subnet, opts ListOpts) ([]Subnet, error) { + + var refinedSubnets []Subnet + var matched bool + m := map[string]interface{}{} + + if opts.ID != "" { + m["ID"] = opts.ID + } + if opts.Name != "" { + m["Name"] = opts.Name + } + if opts.CIDR != "" { + m["CIDR"] = opts.CIDR + } + if opts.Status != "" { + m["Status"] = opts.Status + } + if opts.GatewayIP != "" { + m["GatewayIP"] = opts.GatewayIP + } + if opts.PRIMARY_DNS != "" { + m["PRIMARY_DNS"] = opts.PRIMARY_DNS + } + if opts.SECONDARY_DNS != "" { + m["SECONDARY_DNS"] = opts.SECONDARY_DNS + } + if opts.AvailabilityZone != "" { + m["AvailabilityZone"] = opts.AvailabilityZone + } + if opts.VPC_ID != "" { + m["VPC_ID"] = opts.VPC_ID + } + + if len(m) > 0 && len(subnets) > 0 { + for _, subnet := range subnets { + matched = true + + for key, value := range m { + if sVal := getStructField(&subnet, key); !(sVal == value) { + matched = false + } + } + + if matched { + refinedSubnets = append(refinedSubnets, subnet) + } + } + + } else { + refinedSubnets = subnets + } + + return refinedSubnets, nil +} + +func getStructField(v *Subnet, field string) string { + r := reflect.ValueOf(v) + f := reflect.Indirect(r).FieldByName(field) + return string(f.String()) +} + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToSubnetCreateMap() (map[string]interface{}, error) +} + +// CreateOpts contains all the values needed to create a new subnets. There are +// no required values. +type CreateOpts struct { + Name string `json:"name" required:"true"` + CIDR string `json:"cidr" required:"true"` + GatewayIP string `json:"gateway_ip" required:"true"` + EnableDHCP bool `json:"dhcp_enable,omitempty"` + PRIMARY_DNS string `json:"primary_dns,omitempty"` + SECONDARY_DNS string `json:"secondary_dns,omitempty"` + AvailabilityZone string `json:"availability_zone"` + VPC_ID string `json:"vpc_id" required:"true"` +} + +// ToSubnetCreateMap builds a create request body from CreateOpts. +func (opts CreateOpts) ToSubnetCreateMap() (map[string]interface{}, error) { + return golangsdk.BuildRequestBody(opts, "subnet") +} + +// Create accepts a CreateOpts struct and uses the values to create a new +// logical subnets. When it is created, the subnets does not have an internal +// interface - it is not associated to any subnet. +// +func Create(c *golangsdk.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToSubnetCreateMap() + if err != nil { + r.Err = err + return + } + reqOpt := &golangsdk.RequestOpts{OkCodes: []int{200}} + _, r.Err = c.Post(rootURL(c), b, &r.Body, reqOpt) + return +} + +// Get retrieves a particular subnets based on its unique ID. +func Get(c *golangsdk.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + //ToSubnetUpdateMap() (map[string]interface{}, error) + ToSubnetUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts contains the values used when updating a subnets. +type UpdateOpts struct { + Name string `json:"name,omitempty"` + EnableDHCP bool `json:"dhcp_enable,omitempty"` + PRIMARY_DNS string `json:"primary_dns,omitempty"` + SECONDARY_DNS string `json:"secondary_dns,omitempty"` +} + +// ToSubnetUpdateMap builds an update body based on UpdateOpts. +func (opts UpdateOpts) ToSubnetUpdateMap() (map[string]interface{}, error) { + return golangsdk.BuildRequestBody(opts, "subnet") +} + +// Update allows subnets to be updated. You can update the name, administrative +// state, and the external gateway. +func Update(c *golangsdk.ServiceClient, vpcid string, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToSubnetUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(updateURL(c, vpcid, id), b, &r.Body, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Delete will permanently delete a particular subnets based on its unique ID. +func Delete(c *golangsdk.ServiceClient, vpcid string, id string) (r DeleteResult) { + _, r.Err = c.Delete(updateURL(c, vpcid, id), nil) + return +} diff --git a/openstack/networking/v1/subnets/results.go b/openstack/networking/v1/subnets/results.go new file mode 100644 index 000000000..bf0097955 --- /dev/null +++ b/openstack/networking/v1/subnets/results.go @@ -0,0 +1,116 @@ +package subnets + +import ( + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/pagination" +) + +type Subnet struct { + // ID is the unique identifier for the subnet. + ID string `json:"id"` + + // Name is the human readable name for the subnet. It does not have to be + // unique. + Name string `json:"name"` + + //Specifies the network segment on which the subnet resides. + CIDR string `json:"cidr"` + + //Specifies the IP address list of DNS servers on the subnet. + DnsList []string `json:"dnsList"` + + // Status indicates whether or not a subnet is currently operational. + Status string `json:"status"` + + //Specifies the gateway of the subnet. + GatewayIP string `json:"gateway_ip"` + + //Specifies whether the DHCP function is enabled for the subnet. + EnableDHCP bool `json:"dhcp_enable"` + + //Specifies the IP address of DNS server 1 on the subnet. + PRIMARY_DNS string `json:"primary_dns"` + + //Specifies the IP address of DNS server 2 on the subnet. + SECONDARY_DNS string `json:"secondary_dns"` + + //Identifies the availability zone (AZ) to which the subnet belongs. + AvailabilityZone string `json:"availability_zone"` + + //Specifies the ID of the VPC to which the subnet belongs. + VPC_ID string `json:"vpc_id"` +} + +// SubnetPage is the page returned by a pager when traversing over a +// collection of subnets. +type SubnetPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of subnets has reached +// the end of a page and the pager seeks to traverse over a new one. In order +// to do this, it needs to construct the next page's URL. +func (r SubnetPage) NextPageURL() (string, error) { + var s struct { + Links []golangsdk.Link `json:"subnets_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return golangsdk.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a SubnetPage struct is empty. +func (r SubnetPage) IsEmpty() (bool, error) { + is, err := ExtractSubnets(r) + return len(is) == 0, err +} + +// ExtractSubnets accepts a Page struct, specifically a SubnetPage struct, +// and extracts the elements into a slice of Subnet structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractSubnets(r pagination.Page) ([]Subnet, error) { + var s struct { + Subnets []Subnet `json:"subnets"` + } + err := (r.(SubnetPage)).ExtractInto(&s) + return s.Subnets, err +} + +type commonResult struct { + golangsdk.Result +} + +// Extract is a function that accepts a result and extracts a Subnet. +func (r commonResult) Extract() (*Subnet, error) { + var s struct { + Subnet *Subnet `json:"subnet"` + } + err := r.ExtractInto(&s) + return s.Subnet, err +} + +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a Subnet. +type CreateResult struct { + commonResult +} + +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a Subnet. +type GetResult struct { + commonResult +} + +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a Subnet. +type UpdateResult struct { + commonResult +} + +// DeleteResult represents the result of a delete operation. Call its ExtractErr +// method to determine if the request succeeded or failed. +type DeleteResult struct { + golangsdk.ErrResult +} diff --git a/openstack/networking/v1/subnets/testing/doc.go b/openstack/networking/v1/subnets/testing/doc.go new file mode 100644 index 000000000..77f41573c --- /dev/null +++ b/openstack/networking/v1/subnets/testing/doc.go @@ -0,0 +1,2 @@ +// vpcs unit tests +package testing diff --git a/openstack/networking/v1/subnets/testing/requests_test.go b/openstack/networking/v1/subnets/testing/requests_test.go new file mode 100644 index 000000000..776bf2bdc --- /dev/null +++ b/openstack/networking/v1/subnets/testing/requests_test.go @@ -0,0 +1,265 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + fake "github.com/huaweicloud/golangsdk/openstack/networking/v1/common" + "github.com/huaweicloud/golangsdk/openstack/networking/v1/subnets" + th "github.com/huaweicloud/golangsdk/testhelper" +) + +func TestListSubnet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v1/85636478b0bd8e67e89469c7749d4127/subnets", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` +{ + "subnets": [ + { + "id": "134ca339-24dc-44f5-ae6a-cf0404216ed2", + "name": "openlab-subnet", + "cidr": "192.168.200.0/24", + "status": "ACTIVE", + "vpc_id": "58c24204-170e-4ff0-9b42-c53cdea9239a", + "gateway_ip": "192.168.200.1", + "dhcp_enable": true + }, + { + "id": "134ca339-24dc-44f5-ae6a-cf0404216ed2", + "name": "openlab-subnet", + "cidr": "192.168.200.0/24", + "status": "ACTIVE", + "vpc_id": "58c24204-170e-4ff0-9b42-c53cdea9239a", + "gateway_ip": "192.168.200.1", + "dhcp_enable": true + } + ] +} + + `) + }) + + actual, err := subnets.List(fake.ServiceClient(), subnets.ListOpts{}) + if err != nil { + t.Errorf("Failed to extract subnets: %v", err) + } + + expected := []subnets.Subnet{ + { + Status: "ACTIVE", + CIDR: "192.168.200.0/24", + EnableDHCP: true, + Name: "openlab-subnet", + //DnsList: []string{}, + ID: "134ca339-24dc-44f5-ae6a-cf0404216ed2", + GatewayIP: "192.168.200.1", + VPC_ID: "58c24204-170e-4ff0-9b42-c53cdea9239a", + }, + { + Status: "ACTIVE", + CIDR: "192.168.200.0/24", + EnableDHCP: true, + Name: "openlab-subnet", + //DnsList: []string{}, + ID: "134ca339-24dc-44f5-ae6a-cf0404216ed2", + GatewayIP: "192.168.200.1", + VPC_ID: "58c24204-170e-4ff0-9b42-c53cdea9239a", + }, + /*{ + Status: "ACTIVE", + DnsList: []string{"100.125.4.25", "8.8.8.8"}, + VPC_ID: "e8fab5a4-61d3-4e84-85fd-0049676f926a", + GatewayIP: "192.168.106.1", + PRIMARY_DNS: "100.125.4.25", + SECONDARY_DNS: "8.8.8.8", + CIDR: "192.168.106.0/24", + EnableDHCP: true, + Name: "subnet-a0c9", + ID: "470ef214-acbe-4134-9fbf-1b20f4b8f28d", + },*/ + } + th.AssertDeepEquals(t, expected, actual) +} + +func TestGetSubnet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v1/85636478b0bd8e67e89469c7749d4127/subnets/aab2f0ef-b08b-4f34-9e1a-9f1d8da1afcb", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` +{ + "subnet": { + "id": "aab2f0ef-b08b-4f34-9e1a-9f1d8da1afcb", + "name": "subnet-mgmt", + "cidr": "10.0.0.0/24", + "dnsList": [ + "100.125.4.25", + "8.8.8.8" + ], + "status": "ACTIVE", + "vpc_id": "d4f2c817-d5df-4a66-994a-6571312b470e", + "gateway_ip": "10.0.0.1", + "dhcp_enable": true, + "primary_dns": "100.125.4.25", + "secondary_dns": "8.8.8.8" + } +} + `) + }) + + n, err := subnets.Get(fake.ServiceClient(), "aab2f0ef-b08b-4f34-9e1a-9f1d8da1afcb").Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, "aab2f0ef-b08b-4f34-9e1a-9f1d8da1afcb", n.ID) + th.AssertEquals(t, "subnet-mgmt", n.Name) + th.AssertEquals(t, "10.0.0.0/24", n.CIDR) + th.AssertEquals(t, "ACTIVE", n.Status) + th.AssertEquals(t, "d4f2c817-d5df-4a66-994a-6571312b470e", n.VPC_ID) + th.AssertEquals(t, "10.0.0.1", n.GatewayIP) + th.AssertEquals(t, "100.125.4.25", n.PRIMARY_DNS) + th.AssertEquals(t, "8.8.8.8", n.SECONDARY_DNS) + th.AssertEquals(t, true, n.EnableDHCP) + +} + +func TestCreateSubnet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v1/85636478b0bd8e67e89469c7749d4127/subnets", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` +{ + "subnet": + { + "name": "test_subnets", + "cidr": "192.168.0.0/16", + "gateway_ip": "192.168.0.1", + "primary_dns": "8.8.8.8", + "secondary_dns": "8.8.4.4", + "availability_zone":"eu-de-02", + "vpc_id":"3b9740a0-b44d-48f0-84ee-42eb166e54f7" + } +} + `) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` +{ + "subnet": { + "id": "6b0cf733-f496-4159-9df1-d74c3584a9f7", + "name": "test_subnets", + "cidr": "192.168.0.0/16", + "dnsList": [ + "8.8.8.8", + "8.8.4.4" + ], + "status": "UNKNOWN", + "vpc_id": "3b9740a0-b44d-48f0-84ee-42eb166e54f7", + "gateway_ip": "192.168.0.1", + "dhcp_enable": true, + "primary_dns": "8.8.8.8", + "secondary_dns": "8.8.4.4", + "availability_zone": "eu-de-02" + } +} `) + }) + + options := subnets.CreateOpts{ + Name: "test_subnets", + CIDR: "192.168.0.0/16", + GatewayIP: "192.168.0.1", + PRIMARY_DNS: "8.8.8.8", + SECONDARY_DNS: "8.8.4.4", + AvailabilityZone: "eu-de-02", + VPC_ID: "3b9740a0-b44d-48f0-84ee-42eb166e54f7", + } + n, err := subnets.Create(fake.ServiceClient(), options).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, "test_subnets", n.Name) + th.AssertEquals(t, "192.168.0.1", n.GatewayIP) + th.AssertEquals(t, "192.168.0.0/16", n.CIDR) + th.AssertEquals(t, true, n.EnableDHCP) + th.AssertEquals(t, "8.8.8.8", n.PRIMARY_DNS) + th.AssertEquals(t, "8.8.4.4", n.SECONDARY_DNS) + th.AssertEquals(t, "eu-de-02", n.AvailabilityZone) + th.AssertEquals(t, "6b0cf733-f496-4159-9df1-d74c3584a9f7", n.ID) + th.AssertEquals(t, "UNKNOWN", n.Status) + th.AssertEquals(t, "3b9740a0-b44d-48f0-84ee-42eb166e54f7", n.VPC_ID) + +} + +func TestUpdateSubnet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v1/85636478b0bd8e67e89469c7749d4127/vpcs/8f794f06-2275-4d82-9f5a-6d68fbe21a75/subnets/83e3bddc-b9ed-4614-a0dc-8a997095a86c", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` +{ +"subnet": + { + "name": "testsubnet" + } +} +`) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` +{ + "subnet": { + "id": "83e3bddc-b9ed-4614-a0dc-8a997095a86c", + "name": "testsubnet", + "status": "ACTIVE" + } +} + `) + }) + + options := subnets.UpdateOpts{Name: "testsubnet"} + + n, err := subnets.Update(fake.ServiceClient(), "8f794f06-2275-4d82-9f5a-6d68fbe21a75", "83e3bddc-b9ed-4614-a0dc-8a997095a86c", options).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, "testsubnet", n.Name) + th.AssertEquals(t, "83e3bddc-b9ed-4614-a0dc-8a997095a86c", n.ID) + th.AssertEquals(t, "ACTIVE", n.Status) +} + +func TestDeleteSubnet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v1/85636478b0bd8e67e89469c7749d4127/vpcs/8f794f06-2275-4d82-9f5a-6d68fbe21a75/subnets/83e3bddc-b9ed-4614-a0dc-8a997095a86c", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusNoContent) + }) + + res := subnets.Delete(fake.ServiceClient(), "8f794f06-2275-4d82-9f5a-6d68fbe21a75", "83e3bddc-b9ed-4614-a0dc-8a997095a86c") + th.AssertNoErr(t, res.Err) +} diff --git a/openstack/networking/v1/subnets/urls.go b/openstack/networking/v1/subnets/urls.go new file mode 100644 index 000000000..2ebb20d11 --- /dev/null +++ b/openstack/networking/v1/subnets/urls.go @@ -0,0 +1,20 @@ +package subnets + +import "github.com/huaweicloud/golangsdk" + +const ( + resourcePath = "subnets" + rootpath = "vpcs" +) + +func rootURL(c *golangsdk.ServiceClient) string { + return c.ServiceURL(c.ProjectID, resourcePath) +} + +func resourceURL(c *golangsdk.ServiceClient, id string) string { + return c.ServiceURL(c.ProjectID, resourcePath, id) +} + +func updateURL(c *golangsdk.ServiceClient, vpcid, id string) string { + return c.ServiceURL(c.ProjectID, rootpath, vpcid, resourcePath, id) +} diff --git a/openstack/networking/v1/vpcs/doc.go b/openstack/networking/v1/vpcs/doc.go new file mode 100644 index 000000000..6aa6597c2 --- /dev/null +++ b/openstack/networking/v1/vpcs/doc.go @@ -0,0 +1,52 @@ +/* +Package vpcs enables management and retrieval of Vpcs +VPC service. + +Example to List Vpcs + + listOpts := vpcs.ListOpts{} + allVpcs, err := vpcs.List(vpcClient, listOpts) + if err != nil { + panic(err) + } + + for _, vpc := range allVpcs { + fmt.Printf("%+v\n", vpc) + } + +Example to Create a Vpc + + createOpts := vpcs.CreateOpts{ + Name: "vpc_1", + CIDR: "192.168.0.0/24" + + } + + vpc, err := vpcs.Create(vpcClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Vpc + + vpcID := "4e8e5957-649f-477b-9e5b-f1f75b21c03c" + + updateOpts := vpcs.UpdateOpts{ + Name: "vpc_2", + CIDR: "192.168.0.0/23" + } + + vpc, err := vpcs.Update(vpcClient, vpcID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Vpc + + vpcID := "4e8e5957-649f-477b-9e5b-f1f75b21c03c" + err := vpcs.Delete(vpcClient, vpcID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package vpcs diff --git a/openstack/networking/v1/vpcs/requests.go b/openstack/networking/v1/vpcs/requests.go new file mode 100644 index 000000000..7cfb3ab4a --- /dev/null +++ b/openstack/networking/v1/vpcs/requests.go @@ -0,0 +1,179 @@ +package vpcs + +import ( + "reflect" + + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/pagination" +) + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the floating IP attributes you want to see returned. SortKey allows you to +// sort by a particular network attribute. SortDir sets the direction, and is +// either `asc' or `desc'. Marker and Limit are used for pagination. + +type ListOpts struct { + // ID is the unique identifier for the vpc. + ID string `json:"id"` + + // Name is the human readable name for the vpc. It does not have to be + // unique. + Name string `json:"name"` + + //Specifies the range of available subnets in the VPC. + CIDR string `json:"cidr"` + + // Status indicates whether or not a vpc is currently operational. + Status string `json:"status"` +} + +// List returns collection of +// vpcs. It accepts a ListOpts struct, which allows you to filter and sort +// the returned collection for greater efficiency. +// +// Default policy settings return only those vpcs that are owned by the +// tenant who submits the request, unless an admin user submits the request. +func List(c *golangsdk.ServiceClient, opts ListOpts) ([]Vpc, error) { + u := rootURL(c) + pages, err := pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page { + return VpcPage{pagination.LinkedPageBase{PageResult: r}} + }).AllPages() + + allVpcs, err := ExtractVpcs(pages) + if err != nil { + return nil, err + } + + return FilterVPCs(allVpcs, opts) +} + +func FilterVPCs(vpcs []Vpc, opts ListOpts) ([]Vpc, error) { + + var refinedVPCs []Vpc + var matched bool + m := map[string]interface{}{} + + if opts.ID != "" { + m["ID"] = opts.ID + } + if opts.Name != "" { + m["Name"] = opts.Name + } + if opts.Status != "" { + m["Status"] = opts.Status + } + if opts.CIDR != "" { + m["CIDR"] = opts.CIDR + } + + if len(m) > 0 && len(vpcs) > 0 { + for _, vpc := range vpcs { + matched = true + + for key, value := range m { + if sVal := getStructField(&vpc, key); !(sVal == value) { + matched = false + } + } + + if matched { + refinedVPCs = append(refinedVPCs, vpc) + } + } + + } else { + refinedVPCs = vpcs + } + + return refinedVPCs, nil +} + +func getStructField(v *Vpc, field string) string { + r := reflect.ValueOf(v) + f := reflect.Indirect(r).FieldByName(field) + return string(f.String()) +} + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToVpcCreateMap() (map[string]interface{}, error) +} + +// CreateOpts contains all the values needed to create a new vpc. There are +// no required values. +type CreateOpts struct { + Name string `json:"name,omitempty"` + CIDR string `json:"cidr,omitempty"` +} + +// ToVpcCreateMap builds a create request body from CreateOpts. +func (opts CreateOpts) ToVpcCreateMap() (map[string]interface{}, error) { + return golangsdk.BuildRequestBody(opts, "vpc") +} + +// Create accepts a CreateOpts struct and uses the values to create a new +// logical vpc. When it is created, the vpc does not have an internal +// interface - it is not associated to any subnet. +// +// You can optionally specify an external gateway for a vpc using the +// GatewayInfo struct. The external gateway for the vpc must be plugged into +// an external network (it is external if its `vpc:external' field is set to +// true). +func Create(c *golangsdk.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToVpcCreateMap() + if err != nil { + r.Err = err + return + } + reqOpt := &golangsdk.RequestOpts{OkCodes: []int{200}} + _, r.Err = c.Post(rootURL(c), b, &r.Body, reqOpt) + return +} + +// Get retrieves a particular vpc based on its unique ID. +func Get(c *golangsdk.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToVpcUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts contains the values used when updating a vpc. +type UpdateOpts struct { + CIDR string `json:"cidr,omitempty"` + Name string `json:"name,omitempty"` +} + +// ToVpcUpdateMap builds an update body based on UpdateOpts. +func (opts UpdateOpts) ToVpcUpdateMap() (map[string]interface{}, error) { + return golangsdk.BuildRequestBody(opts, "vpc") +} + +// Update allows vpcs to be updated. You can update the name, administrative +// state, and the external gateway. For more information about how to set the +// external gateway for a vpc, see Create. This operation does not enable +// the update of vpc interfaces. To do this, use the AddInterface and +// RemoveInterface functions. +func Update(c *golangsdk.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToVpcUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Delete will permanently delete a particular vpc based on its unique ID. +func Delete(c *golangsdk.ServiceClient, id string) (r DeleteResult) { + _, r.Err = c.Delete(resourceURL(c, id), nil) + return +} diff --git a/openstack/networking/v1/vpcs/results.go b/openstack/networking/v1/vpcs/results.go new file mode 100644 index 000000000..411aa0dfb --- /dev/null +++ b/openstack/networking/v1/vpcs/results.go @@ -0,0 +1,115 @@ +package vpcs + +import ( + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/pagination" +) + +// Route is a possible route in a vpc. +type Route struct { + NextHop string `json:"nexthop"` + DestinationCIDR string `json:"destination"` +} + +// Vpc represents a Neutron vpc. A vpc is a logical entity that +// forwards packets across internal subnets and NATs (network address +// translation) them on external networks through an appropriate gateway. +// +// A vpc has an interface for each subnet with which it is associated. By +// default, the IP address of such interface is the subnet's gateway IP. Also, +// whenever a vpc is associated with a subnet, a port for that vpc +// interface is added to the subnet's network. +type Vpc struct { + // ID is the unique identifier for the vpc. + ID string `json:"id"` + + // Name is the human readable name for the vpc. It does not have to be + // unique. + Name string `json:"name"` + + //Specifies the range of available subnets in the VPC. + CIDR string `json:"cidr"` + + // Status indicates whether or not a vpc is currently operational. + Status string `json:"status"` + + // Routes are a collection of static routes that the vpc will host. + Routes []Route `json:"routes"` + + //Provides informaion about shared snat + EnableSharedSnat bool `json:"enable_shared_snat"` +} + +// VpcPage is the page returned by a pager when traversing over a +// collection of vpcs. +type VpcPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of vpcs has reached +// the end of a page and the pager seeks to traverse over a new one. In order +// to do this, it needs to construct the next page's URL. +func (r VpcPage) NextPageURL() (string, error) { + var s struct { + Links []golangsdk.Link `json:"vpcs_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return golangsdk.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a VpcPage struct is empty. +func (r VpcPage) IsEmpty() (bool, error) { + is, err := ExtractVpcs(r) + return len(is) == 0, err +} + +// ExtractVpcs accepts a Page struct, specifically a VpcPage struct, +// and extracts the elements into a slice of Vpc structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractVpcs(r pagination.Page) ([]Vpc, error) { + var s struct { + Vpcs []Vpc `json:"vpcs"` + } + err := (r.(VpcPage)).ExtractInto(&s) + return s.Vpcs, err +} + +type commonResult struct { + golangsdk.Result +} + +// Extract is a function that accepts a result and extracts a vpc. +func (r commonResult) Extract() (*Vpc, error) { + var s struct { + Vpc *Vpc `json:"vpc"` + } + err := r.ExtractInto(&s) + return s.Vpc, err +} + +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a Vpc. +type CreateResult struct { + commonResult +} + +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a Vpc. +type GetResult struct { + commonResult +} + +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a Vpc. +type UpdateResult struct { + commonResult +} + +// DeleteResult represents the result of a delete operation. Call its ExtractErr +// method to determine if the request succeeded or failed. +type DeleteResult struct { + golangsdk.ErrResult +} diff --git a/openstack/networking/v1/vpcs/testing/doc.go b/openstack/networking/v1/vpcs/testing/doc.go new file mode 100644 index 000000000..77f41573c --- /dev/null +++ b/openstack/networking/v1/vpcs/testing/doc.go @@ -0,0 +1,2 @@ +// vpcs unit tests +package testing diff --git a/openstack/networking/v1/vpcs/testing/requests_test.go b/openstack/networking/v1/vpcs/testing/requests_test.go new file mode 100644 index 000000000..c2bc2565d --- /dev/null +++ b/openstack/networking/v1/vpcs/testing/requests_test.go @@ -0,0 +1,246 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + fake "github.com/huaweicloud/golangsdk/openstack/networking/v1/common" + "github.com/huaweicloud/golangsdk/openstack/networking/v1/vpcs" + th "github.com/huaweicloud/golangsdk/testhelper" +) + +func TestListVpc(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v1/85636478b0bd8e67e89469c7749d4127/vpcs", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` +{ + "vpcs": [ + { + "id": "14ece7d0-a8d4-4317-982a-041e4f10f442", + "name": "vpc-elb-l00379969", + "cidr": "192.168.0.0/16", + "status": "OK", + "routes": [], + "enable_shared_snat": false + }, + { + "id": "1e5618c3-89f0-4f58-a14e-33536074ec88", + "name": "vpc-ops", + "cidr": "192.168.0.0/16", + "status": "OK", + "routes": [], + "enable_shared_snat": false + }, + { + "id": "2140264c-d313-4363-9874-9a5e18aeb516", + "name": "test", + "cidr": "192.168.0.0/16", + "status": "OK", + "routes": [], + "enable_shared_snat": false + } + ] +} + `) + }) + + //count := 0 + + actual, err := vpcs.List(fake.ServiceClient(), vpcs.ListOpts{}) + if err != nil { + t.Errorf("Failed to extract vpcs: %v", err) + } + + expected := []vpcs.Vpc{ + { + Status: "OK", + CIDR: "192.168.0.0/16", + EnableSharedSnat: false, + Name: "vpc-elb-l00379969", + ID: "14ece7d0-a8d4-4317-982a-041e4f10f442", + Routes: []vpcs.Route{}, + }, + { + Status: "OK", + CIDR: "192.168.0.0/16", + EnableSharedSnat: false, + Name: "vpc-ops", + ID: "1e5618c3-89f0-4f58-a14e-33536074ec88", + Routes: []vpcs.Route{}, + }, + { + Status: "OK", + CIDR: "192.168.0.0/16", + EnableSharedSnat: false, + Name: "test", + ID: "2140264c-d313-4363-9874-9a5e18aeb516", + Routes: []vpcs.Route{}, + }, + } + + th.AssertDeepEquals(t, expected, actual) +} + +func TestGetVpc(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v1/85636478b0bd8e67e89469c7749d4127/vpcs/abda1f6e-ae7c-4ff5-8d06-53425dc11f34", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` +{ + "vpc": { + "id": "abda1f6e-ae7c-4ff5-8d06-53425dc11f34", + "name": "terraform-provider-test-l90006937", + "cidr": "192.168.0.0/16", + "status": "OK", + "routes": [ + { + "destination": "0.0.0.0/0", + "nexthop": "192.168.0.5" + } + ], + "enable_shared_snat": false + } +} + `) + }) + + n, err := vpcs.Get(fake.ServiceClient(), "abda1f6e-ae7c-4ff5-8d06-53425dc11f34").Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, "abda1f6e-ae7c-4ff5-8d06-53425dc11f34", n.ID) + th.AssertEquals(t, "terraform-provider-test-l90006937", n.Name) + th.AssertEquals(t, "192.168.0.0/16", n.CIDR) + th.AssertEquals(t, "OK", n.Status) + th.AssertDeepEquals(t, []vpcs.Route{{DestinationCIDR: "0.0.0.0/0", NextHop: "192.168.0.5"}}, n.Routes) + th.AssertEquals(t, false, n.EnableSharedSnat) + +} + +func TestCreateVpc(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v1/85636478b0bd8e67e89469c7749d4127/vpcs", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` +{ + "vpc": + { + "name": "terraform-provider-vpctestcreate", + "cidr": "192.168.0.0/16" + } +} + `) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` +{ + "vpc": { + "id": "97e01fc2-e39e-4cfc-abf6-1d0886d120af", + "name": "terraform-provider-vpctestcreate", + "cidr": "192.168.0.0/16", + "status": "CREATING" + } +} `) + }) + + options := vpcs.CreateOpts{ + Name: "terraform-provider-vpctestcreate", + CIDR: "192.168.0.0/16", + } + n, err := vpcs.Create(fake.ServiceClient(), options).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, "terraform-provider-vpctestcreate", n.Name) + th.AssertEquals(t, "97e01fc2-e39e-4cfc-abf6-1d0886d120af", n.ID) + th.AssertEquals(t, "192.168.0.0/16", n.CIDR) + th.AssertEquals(t, "CREATING", n.Status) + th.AssertEquals(t, false, n.EnableSharedSnat) +} + +func TestUpdateVpc(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v1/85636478b0bd8e67e89469c7749d4127/vpcs/97e01fc2-e39e-4cfc-abf6-1d0886d120af", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` +{ +"vpc": + { + "name": "terraform-provider-new-name", + "cidr": "192.168.0.0/16" + + } +} + `) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` +{ + "vpc": { + "id": "97e01fc2-e39e-4cfc-abf6-1d0886d120af", + "name": "terraform-provider-new-name", + "cidr": "192.168.0.0/16", + "status": "OK", + "routes": [ + { + "destination": "0.0.0.0/4", + "nexthop": "192.168.0.4" + } + ], + "enable_shared_snat": false + } +} + `) + }) + + options := vpcs.UpdateOpts{Name: "terraform-provider-new-name", CIDR: "192.168.0.0/16"} + + n, err := vpcs.Update(fake.ServiceClient(), "97e01fc2-e39e-4cfc-abf6-1d0886d120af", options).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, "terraform-provider-new-name", n.Name) + th.AssertEquals(t, "97e01fc2-e39e-4cfc-abf6-1d0886d120af", n.ID) + th.AssertEquals(t, "192.168.0.0/16", n.CIDR) + th.AssertEquals(t, "OK", n.Status) + th.AssertDeepEquals(t, []vpcs.Route{{DestinationCIDR: "0.0.0.0/4", NextHop: "192.168.0.4"}}, n.Routes) + th.AssertEquals(t, false, n.EnableSharedSnat) +} + +func TestDeleteVpc(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v1/85636478b0bd8e67e89469c7749d4127/vpcs/abda1f6e-ae7c-4ff5-8d06-53425dc11f34", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusNoContent) + }) + + res := vpcs.Delete(fake.ServiceClient(), "abda1f6e-ae7c-4ff5-8d06-53425dc11f34") + th.AssertNoErr(t, res.Err) +} diff --git a/openstack/networking/v1/vpcs/urls.go b/openstack/networking/v1/vpcs/urls.go new file mode 100644 index 000000000..d4293720f --- /dev/null +++ b/openstack/networking/v1/vpcs/urls.go @@ -0,0 +1,13 @@ +package vpcs + +import "github.com/huaweicloud/golangsdk" + +const resourcePath = "vpcs" + +func rootURL(c *golangsdk.ServiceClient) string { + return c.ServiceURL(c.ProjectID, resourcePath) +} + +func resourceURL(c *golangsdk.ServiceClient, id string) string { + return c.ServiceURL(c.ProjectID, resourcePath, id) +} diff --git a/openstack/networking/v2/peerings/doc.go b/openstack/networking/v2/peerings/doc.go new file mode 100644 index 000000000..c51cb1588 --- /dev/null +++ b/openstack/networking/v2/peerings/doc.go @@ -0,0 +1,87 @@ +/* +Package peerings enables management and retrieval of vpc peering connections + +Example to List a Vpc Peering Connections + listOpts:=peerings.ListOpts{} + + peering,err :=peerings.List(client,sub).AllPages() + + peerings,err:=peerings.ExtractPeerings(peering) + + + if err != nil{ + fmt.Println(err) + } + +Example to Get a Vpc Peering Connection + + peeringID := "6bbacb0f-9f94-4fe8-a6b6-1818bdccb2a3" + + + peering,err :=peerings.Get(client,peeringID).Extract() + + + if err != nil{ + fmt.Println(err) + } + + + +Example to Accept a Vpc Peering Connection Request + // Note:- The TenantId should be of accepter + + peeringID := "6bbacb0f-9f94-4fe8-a6b6-1818bdccb2a3" + + peering,err:=peerings.Accept(client,peeringID).ExtractResult() + + if err != nil{ + fmt.Println(err) + } + + +Example to Reject a Vpc Peering Connection Request + // Note:- The TenantId should be of accepter + peeringID := "6bbacb0f-9f94-4fe8-a6b6-1818bdccb2a3" + + peering,err:=peerings.Reject(client,peeringID).ExtractResult() + + if err != nil{ + fmt.Println(err) + } + +Example to Create a Vpc Peering Connection + + RequestVpcInfo:=peerings.VpcInfo{VpcId:"3127e30b-5f8e-42d1-a3cc-fdadf412c5bf"} + AcceptVpcInfo:=peerings.VpcInfo{"c6efbdb7-dca4-4178-b3ec-692f125c1e25","17fbda95add24720a4038ba4b1c705ed"} + + opt:=peerings.CreateOpts{"C2C_test",RequestVpcInfo,AcceptVpcInfo} + + peering,err:=peerings.Create(client,opt).Extract() + + if err != nil{ + fmt.Println(err) + } + + +Example to Update a VpcPeeringConnection + + peeringID := "6bbacb0f-9f94-4fe8-a6b6-1818bdccb2a3" + + updateOpts:=peerings.UpdateOpts{"C2C_tes1"} + + peering,err:=peerings.Update(client,peeringID,updateOpts).Extract() + + if err != nil{ + fmt.Println(err) + } + + +Example to Delete a VpcPeeringConnection + + peeringID := "6bbacb0f-9f94-4fe8-a6b6-1818bdccb2a3" + err := peerings.Delete(client,"6bbacb0f-9f94-4fe8-a6b6-1818bdccb2a3") + if err != nil { + panic(err) + } +*/ +package peerings diff --git a/openstack/networking/v2/peerings/requests.go b/openstack/networking/v2/peerings/requests.go new file mode 100644 index 000000000..4a26c4726 --- /dev/null +++ b/openstack/networking/v2/peerings/requests.go @@ -0,0 +1,199 @@ +package peerings + +import ( + "reflect" + + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/pagination" +) + +// ListOpts allows the filtering of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the floating IP attributes you want to see returned. +type ListOpts struct { + //ID is the unique identifier for the vpc_peering_connection. + ID string `q:"id"` + + //Name is the human readable name for the vpc_peering_connection. It does not have to be + // unique. + Name string `q:"name"` + + //Status indicates whether or not a vpc_peering_connection is currently operational. + Status string `q:"status"` + + // TenantId indicates vpc_peering_connection avalable in specific tenant. + TenantId string `q:"tenant_id"` + + // VpcId indicates vpc_peering_connection avalable in specific vpc. + VpcId string `q:"vpc_id"` + + // VpcId indicates vpc_peering_connection available in specific vpc. + Peer_VpcId string +} + +func FilterVpcIdParam(opts ListOpts) (filter ListOpts) { + + if opts.VpcId != "" { + filter.VpcId = opts.VpcId + } else { + filter.VpcId = opts.Peer_VpcId + } + + filter.Name = opts.Name + filter.ID = opts.ID + filter.Status = opts.Status + filter.TenantId = opts.TenantId + + return filter +} + +// List returns a Pager which allows you to iterate over a collection of +// vpc_peering_connection resources. It accepts a ListOpts struct, which allows you to +// filter the returned collection for greater efficiency. +func List(c *golangsdk.ServiceClient, opts ListOpts) ([]Peering, error) { + filter := FilterVpcIdParam(opts) + q, err := golangsdk.BuildQueryString(&filter) + if err != nil { + return nil, err + } + u := rootURL(c) + q.String() + pages, err := pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page { + return PeeringConnectionPage{pagination.LinkedPageBase{PageResult: r}} + }).AllPages() + + allPeeringConns, err := ExtractPeerings(pages) + if err != nil { + return nil, err + } + + return FilterVpcPeeringConns(allPeeringConns, opts) +} + +func FilterVpcPeeringConns(peerings []Peering, opts ListOpts) ([]Peering, error) { + var refinedPeerings []Peering + var matched bool + filterMap := map[string]interface{}{} + + if opts.VpcId != "" { + filterMap["RequestVpcInfo"] = opts.VpcId + } + + if opts.Peer_VpcId != "" { + filterMap["AcceptVpcInfo"] = opts.Peer_VpcId + } + + if len(filterMap) > 0 && len(peerings) > 0 { + for _, peering := range peerings { + matched = true + + for key, value := range filterMap { + if sVal := getStructNestedField(&peering, key); !(sVal == value) { + matched = false + } + } + + if matched { + refinedPeerings = append(refinedPeerings, peering) + } + + } + } else { + refinedPeerings = peerings + } + + return refinedPeerings, nil + +} + +func getStructNestedField(v *Peering, field string) string { + r := reflect.ValueOf(v) + f := reflect.Indirect(r).FieldByName(field).Interface() + r1 := reflect.ValueOf(f) + f1 := reflect.Indirect(r1).FieldByName("VpcId") + return string(f1.String()) +} + +func Get(c *golangsdk.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) + return +} + +// Accept is used by a tenant to accept a VPC peering connection request initiated by another tenant. +func Accept(c *golangsdk.ServiceClient, id string) (r AcceptResult) { + _, r.Err = c.Put(acceptURL(c, id), nil, &r.Body, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Reject is used by a tenant to reject a VPC peering connection request initiated by another tenant. +func Reject(c *golangsdk.ServiceClient, id string) (r RejectResult) { + _, r.Err = c.Put(rejectURL(c, id), nil, &r.Body, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +//CreateOptsBuilder is an interface by which can build the request body of vpc peering connection. +type CreateOptsBuilder interface { + ToPeeringCreateMap() (map[string]interface{}, error) +} + +//CreateOpts is a struct which is used to create vpc peering connection. +type CreateOpts struct { + Name string `json:"name"` + RequestVpcInfo VpcInfo `json:"request_vpc_info" required:"true"` + AcceptVpcInfo VpcInfo `json:"accept_vpc_info" required:"true"` +} + +//ToVpcPeeringCreateMap builds a create request body from CreateOpts. +func (opts CreateOpts) ToPeeringCreateMap() (map[string]interface{}, error) { + return golangsdk.BuildRequestBody(opts, "peering") +} + +//Create is a method by which can access to create the vpc peering connection. +func Create(client *golangsdk.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToPeeringCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(rootURL(client), b, &r.Body, &golangsdk.RequestOpts{ + OkCodes: []int{201}, + }) + return +} + +//Delete is a method by which can be able to delete a vpc peering connection. +func Delete(client *golangsdk.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(resourceURL(client, id), nil) + return +} + +//UpdateOptsBuilder is an interface by which can be able to build the request body of vpc peering connection. +type UpdateOptsBuilder interface { + ToVpcPeeringUpdateMap() (map[string]interface{}, error) +} + +//UpdateOpts is a struct which represents the request body of update method. +type UpdateOpts struct { + Name string `json:"name,omitempty"` +} + +//ToVpcPeeringUpdateMap builds a update request body from UpdateOpts. +func (opts UpdateOpts) ToVpcPeeringUpdateMap() (map[string]interface{}, error) { + return golangsdk.BuildRequestBody(opts, "peering") +} + +//Update is a method which can be able to update the name of vpc peering connection. +func Update(client *golangsdk.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToVpcPeeringUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(resourceURL(client, id), b, &r.Body, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/openstack/networking/v2/peerings/results.go b/openstack/networking/v2/peerings/results.go new file mode 100644 index 000000000..1f2cf8394 --- /dev/null +++ b/openstack/networking/v2/peerings/results.go @@ -0,0 +1,141 @@ +package peerings + +import ( + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/pagination" +) + +type VpcInfo struct { + VpcId string `json:"vpc_id" required:"true"` + TenantId string `json:"tenant_id,omitempty"` +} + +// Peering represents a Neutron VPC peering connection. +//Manage and perform other operations on VPC peering connections, +// including querying VPC peering connections as well as +// creating, querying, deleting, and updating a VPC peering connection. +type Peering struct { + // ID is the unique identifier for the vpc_peering_connection. + ID string `json:"id"` + + // Name is the human readable name for the vpc_peering_connection. It does not have to be + // unique. + Name string `json:"name"` + + // Status indicates whether or not a vpc_peering_connections is currently operational. + Status string `json:"status"` + + // RequestVpcInfo indicates information about the local VPC + RequestVpcInfo VpcInfo `json:"request_vpc_info"` + + // AcceptVpcInfo indicates information about the peer VPC + AcceptVpcInfo VpcInfo `json:"accept_vpc_info"` +} + +// PeeringConnectionPage is the page returned by a pager when traversing over a +// collection of vpc_peering_connections. +type PeeringConnectionPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of vpc_peering_connections has reached +// the end of a page and the pager seeks to traverse over a new one. In order +// to do this, it needs to construct the next page's URL. +func (r PeeringConnectionPage) NextPageURL() (string, error) { + var s struct { + Links []golangsdk.Link `json:"peerings_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return golangsdk.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a PeeringConnectionPage struct is empty. +func (r PeeringConnectionPage) IsEmpty() (bool, error) { + is, err := ExtractPeerings(r) + return len(is) == 0, err +} + +// ExtractPeerings accepts a Page struct, specifically a PeeringConnectionPage struct, +// and extracts the elements into a slice of Peering structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractPeerings(r pagination.Page) ([]Peering, error) { + var s struct { + Peerings []Peering `json:"peerings"` + } + err := (r.(PeeringConnectionPage)).ExtractInto(&s) + return s.Peerings, err +} + +type commonResult struct { + golangsdk.Result +} + +// Extract is a function that accepts a result and extracts a Peering. +func (r commonResult) Extract() (*Peering, error) { + var s struct { + Peering *Peering `json:"peering"` + } + err := r.ExtractInto(&s) + return s.Peering, err +} + +// ExtractResult is a function that accepts a result and extracts a Peering. +func (r commonResult) ExtractResult() (Peering, error) { + var s struct { + // ID is the unique identifier for the vpc. + ID string `json:"id"` + // Name is the human readable name for the vpc. It does not have to be + // unique. + Name string `json:"name"` + + // Status indicates whether or not a vpc is currently operational. + Status string `json:"status"` + + // Status indicates whether or not a vpc is currently operational. + RequestVpcInfo VpcInfo `json:"request_vpc_info"` + + //Provides informaion about shared snat + AcceptVpcInfo VpcInfo `json:"accept_vpc_info"` + } + err1 := r.ExtractInto(&s) + return s, err1 +} + +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a Vpc Peering Connection. +type GetResult struct { + commonResult +} + +// AcceptResult represents the result of a get operation. Call its Extract +// method to interpret it as a Vpc Peering Connection. +type AcceptResult struct { + commonResult +} + +// RejectResult represents the result of a get operation. Call its Extract +// method to interpret it as a Vpc Peering Connection. +type RejectResult struct { + commonResult +} + +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a vpc peering connection. +type CreateResult struct { + commonResult +} + +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret it as a vpc peering connection. +type UpdateResult struct { + commonResult +} + +// DeleteResult represents the result of a delete operation. Call its ExtractErr +// method to determine if the request succeeded or failed. +type DeleteResult struct { + golangsdk.ErrResult +} diff --git a/openstack/networking/v2/peerings/testing/doc.go b/openstack/networking/v2/peerings/testing/doc.go new file mode 100644 index 000000000..281471cca --- /dev/null +++ b/openstack/networking/v2/peerings/testing/doc.go @@ -0,0 +1,2 @@ +// peerings unit tests +package testing diff --git a/openstack/networking/v2/peerings/testing/requests_test.go b/openstack/networking/v2/peerings/testing/requests_test.go new file mode 100644 index 000000000..5a080d4c7 --- /dev/null +++ b/openstack/networking/v2/peerings/testing/requests_test.go @@ -0,0 +1,306 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + fake "github.com/huaweicloud/golangsdk/openstack/networking/v2/common" + "github.com/huaweicloud/golangsdk/openstack/networking/v2/peerings" + th "github.com/huaweicloud/golangsdk/testhelper" +) + +func TestListVpcPeerings(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/vpc/peerings", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` +{ + "peerings": [ + { + "status": "PENDING_ACCEPTANCE", + "accept_vpc_info": { + "vpc_id": "c6efbdb7-dca4-4178-b3ec-692f125c1e25", + "tenant_id": "17fbda95add24720a4038ba4b1c705ed" + }, + "request_vpc_info": { + "vpc_id": "3127e30b-5f8e-42d1-a3cc-fdadf412c5bf", + "tenant_id": "87a56a48977e42068f70ad3280c50f0e" + }, + "name": "test_peering", + "id": "22a3e5b1-1150-408e-99f7-5e25a391cead" + }, + { + "status": "ACTIVE", + "accept_vpc_info": { + "vpc_id": "93e94d8e-31a6-4c22-bdf7-8b23c7b67329", + "tenant_id": "87a56a48977e42068f70ad3280c50f0e" + }, + "request_vpc_info": { + "vpc_id": "b0d686e5-312c-4279-b69c-eedbc779ae69", + "tenant_id": "bf74229f30c0421fae270386a43315ee" + }, + "name": "peering-7750-sunway", + "id": "283aabd7-dab4-409d-96ff-6c878b9a0219" + }, + { + "status": "ACTIVE", + "accept_vpc_info": { + "vpc_id": "3127e30b-5f8e-42d1-a3cc-fdadf412c5bf", + "tenant_id": "87a56a48977e42068f70ad3280c50f0e" + }, + "request_vpc_info": { + "vpc_id": "4117d38e-4c8f-4624-a505-bd96b97d024c", + "tenant_id": "87a56a48977e42068f70ad3280c50f0e" + }, + "name": "test", + "id": "71d64714-bd4e-44c4-917a-d8d1239e5292" + } + ] + } + `) + }) + + //count := 0 + actual, err := peerings.List(fake.ServiceClient(), peerings.ListOpts{}) + + if err != nil { + t.Errorf("Failed to extract vpc_peering_connections: %v", err) + } + + expected := []peerings.Peering{ + { + ID: "22a3e5b1-1150-408e-99f7-5e25a391cead", + Name: "test_peering", + Status: "PENDING_ACCEPTANCE", + RequestVpcInfo: peerings.VpcInfo{VpcId: "3127e30b-5f8e-42d1-a3cc-fdadf412c5bf", TenantId: "87a56a48977e42068f70ad3280c50f0e"}, + AcceptVpcInfo: peerings.VpcInfo{VpcId: "c6efbdb7-dca4-4178-b3ec-692f125c1e25", TenantId: "17fbda95add24720a4038ba4b1c705ed"}, + }, + { + ID: "283aabd7-dab4-409d-96ff-6c878b9a0219", + Name: "peering-7750-sunway", + Status: "ACTIVE", + RequestVpcInfo: peerings.VpcInfo{VpcId: "b0d686e5-312c-4279-b69c-eedbc779ae69", TenantId: "bf74229f30c0421fae270386a43315ee"}, + AcceptVpcInfo: peerings.VpcInfo{VpcId: "93e94d8e-31a6-4c22-bdf7-8b23c7b67329", TenantId: "87a56a48977e42068f70ad3280c50f0e"}, + }, + { + ID: "71d64714-bd4e-44c4-917a-d8d1239e5292", + Name: "test", + Status: "ACTIVE", + RequestVpcInfo: peerings.VpcInfo{VpcId: "4117d38e-4c8f-4624-a505-bd96b97d024c", TenantId: "87a56a48977e42068f70ad3280c50f0e"}, + AcceptVpcInfo: peerings.VpcInfo{VpcId: "3127e30b-5f8e-42d1-a3cc-fdadf412c5bf", TenantId: "87a56a48977e42068f70ad3280c50f0e"}, + }, + } + + th.AssertDeepEquals(t, expected, actual) +} + +func TestCreateVpcPeeringConnection(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/vpc/peerings", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` +{ + "peering": { + "name": "test", + + "request_vpc_info": { + "vpc_id": "4117d38e-4c8f-4624-a505-bd96b97d024c" + }, + "accept_vpc_info": { + "vpc_id": "c6efbdb7-dca4-4178-b3ec-692f125c1e25", + "tenant_id": "17fbda95add24720a4038ba4b1c705ed" + } + } +} `) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + fmt.Fprintf(w, ` +{ + "peering": { + "status": "PENDING_ACCEPTANCE", + "accept_vpc_info": { + "vpc_id": "c6efbdb7-dca4-4178-b3ec-692f125c1e25", + "tenant_id": "17fbda95add24720a4038ba4b1c705ed" + }, + "request_vpc_info": { + "vpc_id": "4117d38e-4c8f-4624-a505-bd96b97d024c", + "tenant_id": "87a56a48977e42068f70ad3280c50f0e" + }, + "name": "test", + "id": "4e6ca99d-8344-4eb2-b2c9-b77368db3704" + } +} `) + }) + + options := peerings.CreateOpts{ + Name: "test", + RequestVpcInfo: peerings.VpcInfo{VpcId: "4117d38e-4c8f-4624-a505-bd96b97d024c"}, + AcceptVpcInfo: peerings.VpcInfo{VpcId: "c6efbdb7-dca4-4178-b3ec-692f125c1e25", TenantId: "17fbda95add24720a4038ba4b1c705ed"}, + } + n, err := peerings.Create(fake.ServiceClient(), options).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, "test", n.Name) + th.AssertEquals(t, "4e6ca99d-8344-4eb2-b2c9-b77368db3704", n.ID) + + th.AssertEquals(t, peerings.VpcInfo{VpcId: "4117d38e-4c8f-4624-a505-bd96b97d024c", TenantId: "87a56a48977e42068f70ad3280c50f0e"}, n.RequestVpcInfo) + th.AssertEquals(t, peerings.VpcInfo{VpcId: "c6efbdb7-dca4-4178-b3ec-692f125c1e25", TenantId: "17fbda95add24720a4038ba4b1c705ed"}, n.AcceptVpcInfo) + th.AssertEquals(t, "PENDING_ACCEPTANCE", n.Status) +} + +func TestUpdateVpcPeeringConnection(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/vpc/peerings/4e6ca99d-8344-4eb2-b2c9-b77368db3704", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` +{ + "peering": { + "name": "test2" + } +}`) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` +{ + "peering": { + "status": "PENDING_ACCEPTANCE", + "accept_vpc_info": { + "vpc_id": "c6efbdb7-dca4-4178-b3ec-692f125c1e25", + "tenant_id": "17fbda95add24720a4038ba4b1c705ed" + }, + "request_vpc_info": { + "vpc_id": "4117d38e-4c8f-4624-a505-bd96b97d024c", + "tenant_id": "87a56a48977e42068f70ad3280c50f0e" + }, + "name": "test2", + "id": "4e6ca99d-8344-4eb2-b2c9-b77368db3704" + } +} + `) + }) + + options := peerings.UpdateOpts{Name: "test2"} + + n, err := peerings.Update(fake.ServiceClient(), "4e6ca99d-8344-4eb2-b2c9-b77368db3704", options).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, "test2", n.Name) + th.AssertEquals(t, "4e6ca99d-8344-4eb2-b2c9-b77368db3704", n.ID) + th.AssertEquals(t, peerings.VpcInfo{VpcId: "4117d38e-4c8f-4624-a505-bd96b97d024c", TenantId: "87a56a48977e42068f70ad3280c50f0e"}, n.RequestVpcInfo) + th.AssertEquals(t, peerings.VpcInfo{VpcId: "c6efbdb7-dca4-4178-b3ec-692f125c1e25", TenantId: "17fbda95add24720a4038ba4b1c705ed"}, n.AcceptVpcInfo) + th.AssertEquals(t, "PENDING_ACCEPTANCE", n.Status) +} + +func TestDeleteVpcPeeringConnection(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/vpc/peerings/4e6ca99d-8344-4eb2-b2c9-b77368db3704", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusNoContent) + }) + + res := peerings.Delete(fake.ServiceClient(), "4e6ca99d-8344-4eb2-b2c9-b77368db3704") + th.AssertNoErr(t, res.Err) +} + +func TestAcceptVpcPeering(t *testing.T) { + + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/vpc/peerings/22a3e5b1-1150-408e-99f7-5e25a391cead/accept", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` { + "status": "ACTIVE", + "name": "test_peering", + "tenant_id": "17fbda95add24720a4038ba4b1c705ed", + "request_vpc_info": { + "vpc_id": "3127e30b-5f8e-42d1-a3cc-fdadf412c5bf", + "tenant_id": "87a56a48977e42068f70ad3280c50f0e" + }, + "accept_vpc_info": { + "vpc_id": "c6efbdb7-dca4-4178-b3ec-692f125c1e25", + "tenant_id": "17fbda95add24720a4038ba4b1c705ed" + }, + "id": "22a3e5b1-1150-408e-99f7-5e25a391cead" + } + `) + }) + + n, err := peerings.Accept(fake.ServiceClient(), "22a3e5b1-1150-408e-99f7-5e25a391cead").ExtractResult() + th.AssertNoErr(t, err) + th.AssertEquals(t, "22a3e5b1-1150-408e-99f7-5e25a391cead", n.ID) + th.AssertEquals(t, "test_peering", n.Name) + th.AssertEquals(t, "ACTIVE", n.Status) + th.AssertDeepEquals(t, peerings.VpcInfo{VpcId: "c6efbdb7-dca4-4178-b3ec-692f125c1e25", TenantId: "17fbda95add24720a4038ba4b1c705ed"}, n.AcceptVpcInfo) + th.AssertDeepEquals(t, peerings.VpcInfo{VpcId: "3127e30b-5f8e-42d1-a3cc-fdadf412c5bf", TenantId: "87a56a48977e42068f70ad3280c50f0e"}, n.RequestVpcInfo) + +} + +func TestRejectVpcPeering(t *testing.T) { + + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/vpc/peerings/22a3e5b1-1150-408e-99f7-5e25a391cead/reject", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Accept", "application/json") + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` { + "status": "ACTIVE", + "name": "test_peering", + "tenant_id": "17fbda95add24720a4038ba4b1c705ed", + "request_vpc_info": { + "vpc_id": "3127e30b-5f8e-42d1-a3cc-fdadf412c5bf", + "tenant_id": "87a56a48977e42068f70ad3280c50f0e" + }, + "accept_vpc_info": { + "vpc_id": "c6efbdb7-dca4-4178-b3ec-692f125c1e25", + "tenant_id": "17fbda95add24720a4038ba4b1c705ed" + }, + "id": "22a3e5b1-1150-408e-99f7-5e25a391cead" + } + `) + }) + + n, err := peerings.Reject(fake.ServiceClient(), "22a3e5b1-1150-408e-99f7-5e25a391cead").ExtractResult() + th.AssertNoErr(t, err) + th.AssertEquals(t, "22a3e5b1-1150-408e-99f7-5e25a391cead", n.ID) + th.AssertEquals(t, "test_peering", n.Name) + th.AssertEquals(t, "ACTIVE", n.Status) + th.AssertDeepEquals(t, peerings.VpcInfo{VpcId: "c6efbdb7-dca4-4178-b3ec-692f125c1e25", TenantId: "17fbda95add24720a4038ba4b1c705ed"}, n.AcceptVpcInfo) + th.AssertDeepEquals(t, peerings.VpcInfo{VpcId: "3127e30b-5f8e-42d1-a3cc-fdadf412c5bf", TenantId: "87a56a48977e42068f70ad3280c50f0e"}, n.RequestVpcInfo) + +} diff --git a/openstack/networking/v2/peerings/urls.go b/openstack/networking/v2/peerings/urls.go new file mode 100644 index 000000000..06ad30910 --- /dev/null +++ b/openstack/networking/v2/peerings/urls.go @@ -0,0 +1,24 @@ +package peerings + +import "github.com/huaweicloud/golangsdk" + +const ( + resourcePath = "peerings" + rootpath = "vpc" +) + +func rootURL(c *golangsdk.ServiceClient) string { + return c.ServiceURL(rootpath, resourcePath) +} + +func resourceURL(c *golangsdk.ServiceClient, id string) string { + return c.ServiceURL(rootpath, resourcePath, id) +} + +func acceptURL(c *golangsdk.ServiceClient, id string) string { + return c.ServiceURL(rootpath, resourcePath, id, "accept") +} + +func rejectURL(c *golangsdk.ServiceClient, id string) string { + return c.ServiceURL(rootpath, resourcePath, id, "reject") +} diff --git a/openstack/networking/v2/routes/doc.go b/openstack/networking/v2/routes/doc.go new file mode 100644 index 000000000..8b3ae0bbc --- /dev/null +++ b/openstack/networking/v2/routes/doc.go @@ -0,0 +1,28 @@ +/* +Package routes enables management and retrieval of Routes +Route service. + +Example to List Routes + + listroute:=routes.ListOpts{VPC_ID:"93e94d8e-31a6-4c22-bdf7-8b23c7b67329"} + out,err:=routes.List(client,listroute) + fmt.Println(out[0].RouteID) + + +Example to Create a Route + + route:=routes.CreateOpts{ + Type:"peering", + NextHop:"d2dea4ba-e988-4e9c-8162-652e74b2560c", + Destination:"192.168.0.0/16", + VPC_ID:"3127e30b-5f8e-42d1-a3cc-fdadf412c5bf"} + outroute,err:=routes.Create(client,route).Extract() + fmt.Println(outroute) + + +Example to Delete a Route + + out:=routes.Delete(client,"39a07dcb-f30e-41c1-97ac-182c8f0d43c1") + fmt.Println(out) +*/ +package routes diff --git a/openstack/networking/v2/routes/requests.go b/openstack/networking/v2/routes/requests.go new file mode 100644 index 000000000..6d0de1e16 --- /dev/null +++ b/openstack/networking/v2/routes/requests.go @@ -0,0 +1,108 @@ +package routes + +import ( + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/pagination" +) + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the attributes you want to see returned. +type ListOpts struct { + // Specifies the route type. + Type string `q:"type"` + + // Specifies the next hop. If the route type is peering, enter the VPC peering connection ID. + //NextHop string `q:"nexthop"` + + //Specifies the destination IP address or CIDR block. + Destination string `q:"destination"` + + // Specifies the VPC for which a route is to be added. + VPC_ID string `q:"vpc_id"` + + //Specifies the tenant ID. Only the administrator can specify the tenant ID of other tenants. + Tenant_Id string `q:"tenant_id"` + + //Specifies the route ID. + RouteID string `q:"id"` +} +type ListOptsBuilder interface { + ToRouteListQuery() (string, error) +} + +// ToRouteListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToRouteListQuery() (string, error) { + q, err := golangsdk.BuildQueryString(opts) + if err != nil { + return "", err + } + return q.String(), nil +} + +// List returns a Pager which allows you to iterate over a collection of +// vpc routes resources. It accepts a ListOpts struct, which allows you to +// filter the returned collection for greater efficiency. +func List(c *golangsdk.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + + if opts != nil { + query, err := opts.ToRouteListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return RoutePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToRouteCreateMap() (map[string]interface{}, error) +} + +// CreateOpts contains all the values needed to create a new routes. There are +// no required values. +type CreateOpts struct { + Type string `json:"type,omitempty" required:"true"` + NextHop string `json:"nexthop,omitempty" required:"true"` + Destination string `json:"destination,omitempty" required:"true"` + Tenant_Id string `json:"tenant_id,omitempty"` + VPC_ID string `json:"vpc_id,omitempty" required:"true"` +} + +// ToRouteCreateMap builds a create request body from CreateOpts. +func (opts CreateOpts) ToRouteCreateMap() (map[string]interface{}, error) { + return golangsdk.BuildRequestBody(opts, "route") +} + +// Create accepts a CreateOpts struct and uses the values to create a new +// logical routes. When it is created, the routes does not have an internal +// interface - it is not associated to any routes. +// +func Create(c *golangsdk.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToRouteCreateMap() + if err != nil { + r.Err = err + return + } + reqOpt := &golangsdk.RequestOpts{OkCodes: []int{201}} + _, r.Err = c.Post(rootURL(c), b, &r.Body, reqOpt) + return +} + +// Get retrieves a particular route based on its unique ID. +func Get(c *golangsdk.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) + return +} + +// Delete will permanently delete a particular route based on its unique ID. +func Delete(c *golangsdk.ServiceClient, id string) (r DeleteResult) { + _, r.Err = c.Delete(resourceURL(c, id), nil) + return +} diff --git a/openstack/networking/v2/routes/results.go b/openstack/networking/v2/routes/results.go new file mode 100644 index 000000000..ef97c904e --- /dev/null +++ b/openstack/networking/v2/routes/results.go @@ -0,0 +1,94 @@ +package routes + +import ( + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/pagination" +) + +type Route struct { + // Specifies the route type. + Type string `json:"type"` + + // Specifies the next hop. If the route type is peering, enter the VPC peering connection ID. + NextHop string `json:"nexthop"` + + //Specifies the destination IP address or CIDR block. + Destination string `json:"destination"` + + // Specifies the VPC for which a route is to be added. + VPC_ID string `json:"vpc_id"` + + //Specifies the tenant ID. Only the administrator can specify the tenant ID of other tenants. + Tenant_Id string `json:"tenant_id"` + + //Specifies the route ID. + RouteID string `json:"id"` +} + +// RoutePage is the page returned by a pager when traversing over a +// collection of routes. +type RoutePage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of routes has reached +// the end of a page and the pager seeks to traverse over a new one. In order +// to do this, it needs to construct the next page's URL. +func (r RoutePage) NextPageURL() (string, error) { + var s struct { + Links []golangsdk.Link `json:"routes_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return golangsdk.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a RoutePage struct is empty. +func (r RoutePage) IsEmpty() (bool, error) { + is, err := ExtractRoutes(r) + return len(is) == 0, err +} + +// ExtractRoutes accepts a Page struct, specifically a RoutePage struct, +// and extracts the elements into a slice of Roue structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractRoutes(r pagination.Page) ([]Route, error) { + var s struct { + Routes []Route `json:"routes"` + } + err := (r.(RoutePage)).ExtractInto(&s) + return s.Routes, err +} + +type commonResult struct { + golangsdk.Result +} + +// Extract is a function that accepts a result and extracts a Route. +func (r commonResult) Extract() (*Route, error) { + var s struct { + Route *Route `json:"route"` + } + err := r.ExtractInto(&s) + return s.Route, err +} + +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret it as a Route. +type CreateResult struct { + commonResult +} + +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a Route. +type GetResult struct { + commonResult +} + +// DeleteResult represents the result of a delete operation. Call its ExtractErr +// method to determine if the request succeeded or failed. +type DeleteResult struct { + golangsdk.ErrResult +} diff --git a/openstack/networking/v2/routes/testing/doc.go b/openstack/networking/v2/routes/testing/doc.go new file mode 100644 index 000000000..7603f836a --- /dev/null +++ b/openstack/networking/v2/routes/testing/doc.go @@ -0,0 +1 @@ +package testing diff --git a/openstack/networking/v2/routes/testing/requests_test.go b/openstack/networking/v2/routes/testing/requests_test.go new file mode 100644 index 000000000..61f5c6ffe --- /dev/null +++ b/openstack/networking/v2/routes/testing/requests_test.go @@ -0,0 +1,194 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + fake "github.com/huaweicloud/golangsdk/openstack/networking/v2/common" + "github.com/huaweicloud/golangsdk/openstack/networking/v2/routes" + th "github.com/huaweicloud/golangsdk/testhelper" +) + +func TestListRoutes(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/vpc/routes", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` +{ + "routes": [ + { + "destination": "172.31.8.192/26", + "tenant_id": "87a56a48977e42068f70ad3280c50f0e", + "nexthop": "283aabd7-dab4-409d-96ff-6c878b9a0219", + "vpc_id": "93e94d8e-31a6-4c22-bdf7-8b23c7b67329", + "type": "peering", + "id": "4ae95cd4-292d-4a27-b3de-1be835eb32e1" + }, + { + "destination": "172.31.8.128/26", + "tenant_id": "87a56a48977e42068f70ad3280c50f0e", + "nexthop": "283aabd7-dab4-409d-96ff-6c878b9a0219", + "vpc_id": "93e94d8e-31a6-4c22-bdf7-8b23c7b67329", + "type": "peering", + "id": "804d5d09-cee2-418d-8d1b-be29b8e8e9e8" + }, + { + "destination": "172.31.8.112/28", + "tenant_id": "87a56a48977e42068f70ad3280c50f0e", + "nexthop": "283aabd7-dab4-409d-96ff-6c878b9a0219", + "vpc_id": "93e94d8e-31a6-4c22-bdf7-8b23c7b67329", + "type": "peering", + "id": "9f54e4ac-e052-4198-bb73-51b22ad41035" + } + ] +} + `) + }) + + pages, err := routes.List(fake.ServiceClient(), routes.ListOpts{}).AllPages() + if err != nil { + t.Errorf("Failed to get routes: %v", err) + } + + actual, err := routes.ExtractRoutes(pages) + if err != nil { + t.Errorf("Failed to extract routes: %v", err) + } + + expected := []routes.Route{ + { + Type: "peering", + NextHop: "283aabd7-dab4-409d-96ff-6c878b9a0219", + Destination: "172.31.8.192/26", + VPC_ID: "93e94d8e-31a6-4c22-bdf7-8b23c7b67329", + Tenant_Id: "87a56a48977e42068f70ad3280c50f0e", + RouteID: "4ae95cd4-292d-4a27-b3de-1be835eb32e1", + }, + { + Type: "peering", + NextHop: "283aabd7-dab4-409d-96ff-6c878b9a0219", + Destination: "172.31.8.128/26", + VPC_ID: "93e94d8e-31a6-4c22-bdf7-8b23c7b67329", + Tenant_Id: "87a56a48977e42068f70ad3280c50f0e", + RouteID: "804d5d09-cee2-418d-8d1b-be29b8e8e9e8", + }, + { + Type: "peering", + NextHop: "283aabd7-dab4-409d-96ff-6c878b9a0219", + Destination: "172.31.8.112/28", + VPC_ID: "93e94d8e-31a6-4c22-bdf7-8b23c7b67329", + Tenant_Id: "87a56a48977e42068f70ad3280c50f0e", + RouteID: "9f54e4ac-e052-4198-bb73-51b22ad41035", + }, + } + + th.AssertDeepEquals(t, expected, actual) +} + +func TestGetRoutes(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/vpc/routes/39a07dcb-f30e-41c1-97ac-182c8f0d43c1", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, ` +{ + "route": { + "destination": "192.168.0.0/16", + "tenant_id": "87a56a48977e42068f70ad3280c50f0e", + "nexthop": "d2dea4ba-e988-4e9c-8162-652e74b2560c", + "vpc_id": "3127e30b-5f8e-42d1-a3cc-fdadf412c5bf", + "type": "peering", + "id": "39a07dcb-f30e-41c1-97ac-182c8f0d43c1" + } +} + `) + }) + + n, err := routes.Get(fake.ServiceClient(), "39a07dcb-f30e-41c1-97ac-182c8f0d43c1").Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, "39a07dcb-f30e-41c1-97ac-182c8f0d43c1", n.RouteID) + th.AssertEquals(t, "192.168.0.0/16", n.Destination) + th.AssertEquals(t, "87a56a48977e42068f70ad3280c50f0e", n.Tenant_Id) + th.AssertEquals(t, "d2dea4ba-e988-4e9c-8162-652e74b2560c", n.NextHop) + th.AssertEquals(t, "3127e30b-5f8e-42d1-a3cc-fdadf412c5bf", n.VPC_ID) + th.AssertEquals(t, "peering", n.Type) +} + +func TestCreateRoute(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/vpc/routes", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, ` +{ + "route": { + "type": "peering", + "nexthop": "d2dea4ba-e988-4e9c-8162-652e74b2560c", + "destination": "192.168.0.0/16", + "vpc_id": "3127e30b-5f8e-42d1-a3cc-fdadf412c5bf" + } +} + `) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + fmt.Fprintf(w, ` +{ + "route": { + "destination": "192.168.0.0/16", + "tenant_id": "87a56a48977e42068f70ad3280c50f0e", + "nexthop": "d2dea4ba-e988-4e9c-8162-652e74b2560c", + "vpc_id": "3127e30b-5f8e-42d1-a3cc-fdadf412c5bf", + "type": "peering", + "id": "39a07dcb-f30e-41c1-97ac-182c8f0d43c1" + } +} `) + }) + + options := routes.CreateOpts{ + Type: "peering", + NextHop: "d2dea4ba-e988-4e9c-8162-652e74b2560c", + Destination: "192.168.0.0/16", + VPC_ID: "3127e30b-5f8e-42d1-a3cc-fdadf412c5bf", + } + n, err := routes.Create(fake.ServiceClient(), options).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, "peering", n.Type) + th.AssertEquals(t, "d2dea4ba-e988-4e9c-8162-652e74b2560c", n.NextHop) + th.AssertEquals(t, "192.168.0.0/16", n.Destination) + th.AssertEquals(t, "3127e30b-5f8e-42d1-a3cc-fdadf412c5bf", n.VPC_ID) + th.AssertEquals(t, "39a07dcb-f30e-41c1-97ac-182c8f0d43c1", n.RouteID) +} + +func TestDeleteRoute(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/v2.0/vpc/routes/39a07dcb-f30e-41c1-97ac-182c8f0d43c1", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusNoContent) + }) + + res := routes.Delete(fake.ServiceClient(), "39a07dcb-f30e-41c1-97ac-182c8f0d43c1") + th.AssertNoErr(t, res.Err) +} diff --git a/openstack/networking/v2/routes/urls.go b/openstack/networking/v2/routes/urls.go new file mode 100644 index 000000000..c3cfe2b9f --- /dev/null +++ b/openstack/networking/v2/routes/urls.go @@ -0,0 +1,14 @@ +package routes + +import "github.com/huaweicloud/golangsdk" + +const resourcePath = "routes" +const rootPath = "vpc" + +func rootURL(c *golangsdk.ServiceClient) string { + return c.ServiceURL(rootPath, resourcePath) +} + +func resourceURL(c *golangsdk.ServiceClient, id string) string { + return c.ServiceURL(rootPath, resourcePath, id) +}