Merge pull request #9739 from olemarkus/openstack-floatingip-test

Add an integration test for openstack floating ip
This commit is contained in:
Kubernetes Prow Robot 2020-08-12 05:39:46 -07:00 committed by GitHub
commit c10d6c4710
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 236 additions and 10 deletions

View File

@ -4,6 +4,7 @@ go_library(
name = "go_default_library", name = "go_default_library",
srcs = [ srcs = [
"api.go", "api.go",
"floatingips.go",
"networks.go", "networks.go",
"ports.go", "ports.go",
"routers.go", "routers.go",
@ -16,6 +17,7 @@ go_library(
deps = [ deps = [
"//cloudmock/openstack:go_default_library", "//cloudmock/openstack:go_default_library",
"//vendor/github.com/google/uuid:go_default_library", "//vendor/github.com/google/uuid:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules:go_default_library",

View File

@ -20,6 +20,8 @@ import (
"net/http/httptest" "net/http/httptest"
"sync" "sync"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules"
@ -40,6 +42,7 @@ type MockClient struct {
securityGroups map[string]groups.SecGroup securityGroups map[string]groups.SecGroup
securityGroupRules map[string]rules.SecGroupRule securityGroupRules map[string]rules.SecGroupRule
subnets map[string]subnets.Subnet subnets map[string]subnets.Subnet
floatingips map[string]floatingips.FloatingIP
} }
// CreateClient will create a new mock networking client // CreateClient will create a new mock networking client
@ -53,6 +56,7 @@ func CreateClient() *MockClient {
m.mockSecurityGroups() m.mockSecurityGroups()
m.mockSecurityGroupRules() m.mockSecurityGroupRules()
m.mockSubnets() m.mockSubnets()
m.mockFloatingIPs()
m.Server = httptest.NewServer(m.Mux) m.Server = httptest.NewServer(m.Mux)
return m return m
} }
@ -66,6 +70,7 @@ func (m *MockClient) Reset() {
m.securityGroups = make(map[string]groups.SecGroup) m.securityGroups = make(map[string]groups.SecGroup)
m.securityGroupRules = make(map[string]rules.SecGroupRule) m.securityGroupRules = make(map[string]rules.SecGroupRule)
m.subnets = make(map[string]subnets.Subnet) m.subnets = make(map[string]subnets.Subnet)
m.floatingips = make(map[string]floatingips.FloatingIP)
} }
// All returns a map of all resource IDs to their resources // All returns a map of all resource IDs to their resources
@ -92,5 +97,8 @@ func (m *MockClient) All() map[string]interface{} {
for id, s := range m.subnets { for id, s := range m.subnets {
all[id] = s all[id] = s
} }
for id, s := range m.floatingips {
all[id] = s
}
return all return all
} }

View File

@ -0,0 +1,76 @@
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package mocknetworking
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"regexp"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
)
type floatingIPListResponse struct {
FloatingIPs []floatingips.FloatingIP `json:"floatingips"`
}
func (m *MockClient) mockFloatingIPs() {
re := regexp.MustCompile(`/floatingips/?`)
handler := func(w http.ResponseWriter, r *http.Request) {
m.mutex.Lock()
defer m.mutex.Unlock()
w.Header().Add("Content-Type", "application/json")
floatingIPID := re.ReplaceAllString(r.URL.Path, "")
switch r.Method {
case http.MethodGet:
if floatingIPID == "" {
r.ParseForm()
m.listFloatingIPs(w, r.Form)
}
default:
w.WriteHeader(http.StatusBadRequest)
}
}
m.Mux.HandleFunc("/floatingips/", handler)
m.Mux.HandleFunc("/floatingips", handler)
}
func (m *MockClient) listFloatingIPs(w http.ResponseWriter, vals url.Values) {
w.WriteHeader(http.StatusOK)
floatingips := make([]floatingips.FloatingIP, 0)
for _, p := range m.floatingips {
floatingips = append(floatingips, p)
}
resp := floatingIPListResponse{
FloatingIPs: floatingips,
}
respB, err := json.Marshal(resp)
if err != nil {
panic(fmt.Sprintf("failed to marshal %+v", resp))
}
_, err = w.Write(respB)
if err != nil {
panic("failed to write body")
}
}

View File

@ -78,10 +78,22 @@ func (m *MockClient) listPorts(w http.ResponseWriter, vals url.Values) {
ports := make([]ports.Port, 0) ports := make([]ports.Port, 0)
nameFilter := vals.Get("name") nameFilter := vals.Get("name")
idFilter := vals.Get("id")
networkFilter := vals.Get("network_id")
deviceFilter := vals.Get("device_id")
for _, p := range m.ports { for _, p := range m.ports {
if nameFilter != "" && nameFilter != p.Name { if nameFilter != "" && nameFilter != p.Name {
continue continue
} }
if deviceFilter != "" && deviceFilter != p.DeviceID {
continue
}
if networkFilter != "" && networkFilter != p.NetworkID {
continue
}
if idFilter != "" && idFilter != p.ID {
continue
}
ports = append(ports, p) ports = append(ports, p)
} }
@ -152,6 +164,7 @@ func (m *MockClient) createPort(w http.ResponseWriter, r *http.Request) {
Name: create.Port.Name, Name: create.Port.Name,
NetworkID: create.Port.NetworkID, NetworkID: create.Port.NetworkID,
SecurityGroups: *create.Port.SecurityGroups, SecurityGroups: *create.Port.SecurityGroups,
DeviceID: create.Port.DeviceID,
FixedIPs: fixedIPs, FixedIPs: fixedIPs,
} }
m.ports[p.ID] = p m.ports[p.ID] = p

View File

@ -24,6 +24,8 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers"
) )
@ -78,10 +80,14 @@ func (m *MockClient) listRouters(w http.ResponseWriter, vals url.Values) {
routers := make([]routers.Router, 0) routers := make([]routers.Router, 0)
nameFilter := vals.Get("name") nameFilter := vals.Get("name")
idFilter := vals.Get("id")
for _, r := range m.routers { for _, r := range m.routers {
if nameFilter != "" && r.Name != nameFilter { if nameFilter != "" && r.Name != nameFilter {
continue continue
} }
if idFilter != "" && r.ID != idFilter {
continue
}
routers = append(routers, r) routers = append(routers, r)
} }
@ -177,11 +183,25 @@ func (m *MockClient) routerInterface(w http.ResponseWriter, r *http.Request) {
panic("error decoding create router interface request") panic("error decoding create router interface request")
} }
if parts[2] == "add_router_interface" { if parts[2] == "add_router_interface" {
subnet := m.subnets[createInterface.SubnetID]
interfaces := m.routerInterfaces[routerID] interfaces := m.routerInterfaces[routerID]
interfaces = append(interfaces, routers.InterfaceInfo{ interfaces = append(interfaces, routers.InterfaceInfo{
SubnetID: createInterface.SubnetID, SubnetID: subnet.ID,
}) })
m.routerInterfaces[routerID] = interfaces m.routerInterfaces[routerID] = interfaces
// If PortID is not sent, this creates a new port.
port := ports.Port{
ID: uuid.New().String(),
NetworkID: subnet.NetworkID,
DeviceID: routerID,
FixedIPs: []ports.IP{
{
SubnetID: subnet.ID,
},
},
}
m.ports[port.ID] = port
} else if parts[2] == "remove_router_interface" { } else if parts[2] == "remove_router_interface" {
interfaces := make([]routers.InterfaceInfo, 0) interfaces := make([]routers.InterfaceInfo, 0)
for _, i := range m.routerInterfaces[routerID] { for _, i := range m.routerInterfaces[routerID] {

View File

@ -73,6 +73,14 @@ func TestLifecycleMinimalOpenstack(t *testing.T) {
}) })
} }
func TestLifecycleFloatingIPOpenstack(t *testing.T) {
runLifecycleTestOpenstack(&LifecycleTestOptions{
t: t,
SrcDir: "openstack_floatingip",
ClusterName: "floatingip-openstack.k8s.local",
})
}
// TestLifecyclePrivateCalico runs the test on a private topology // TestLifecyclePrivateCalico runs the test on a private topology
func TestLifecyclePrivateCalico(t *testing.T) { func TestLifecyclePrivateCalico(t *testing.T) {
runLifecycleTestAWS(&LifecycleTestOptions{ runLifecycleTestAWS(&LifecycleTestOptions{

View File

@ -22,6 +22,9 @@ import (
) )
func openstackValidateCluster(c *kops.Cluster) (errList field.ErrorList) { func openstackValidateCluster(c *kops.Cluster) (errList field.ErrorList) {
if c.Spec.CloudConfig == nil || c.Spec.CloudConfig.Openstack == nil {
return errList
}
if c.Spec.CloudConfig.Openstack.Router == nil || c.Spec.CloudConfig.Openstack.Router.ExternalNetwork == nil { if c.Spec.CloudConfig.Openstack.Router == nil || c.Spec.CloudConfig.Openstack.Router.ExternalNetwork == nil {
topology := c.Spec.Topology topology := c.Spec.Topology
if topology == nil || topology.Nodes == kops.TopologyPublic { if topology == nil || topology.Nodes == kops.TopologyPublic {

View File

@ -18,6 +18,7 @@ go_library(
importpath = "k8s.io/kops/pkg/resources/openstack", importpath = "k8s.io/kops/pkg/resources/openstack",
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//pkg/dns:go_default_library",
"//pkg/resources:go_default_library", "//pkg/resources:go_default_library",
"//upup/pkg/fi:go_default_library", "//upup/pkg/fi:go_default_library",
"//upup/pkg/fi/cloudup/openstack:go_default_library", "//upup/pkg/fi/cloudup/openstack:go_default_library",

View File

@ -19,6 +19,8 @@ package openstack
import ( import (
"fmt" "fmt"
"k8s.io/kops/pkg/dns"
"github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets" "github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets"
"github.com/gophercloud/gophercloud/openstack/dns/v2/zones" "github.com/gophercloud/gophercloud/openstack/dns/v2/zones"
"k8s.io/kops/pkg/resources" "k8s.io/kops/pkg/resources"
@ -31,13 +33,14 @@ const (
) )
func (os *clusterDiscoveryOS) ListDNSRecordsets() ([]*resources.Resource, error) { func (os *clusterDiscoveryOS) ListDNSRecordsets() ([]*resources.Resource, error) {
zopts := zones.ListOpts{
Name: os.clusterName, // if dnsclient does not exist (designate disabled) or using gossip DNS
if os.osCloud.DNSClient() == nil || dns.IsGossipHostname(os.clusterName) {
return nil, nil
} }
// if dnsclient does not exist (designate disabled) zopts := zones.ListOpts{
if os.osCloud.DNSClient() == nil { Name: os.clusterName,
return nil, nil
} }
zs, err := os.osCloud.ListDNSZones(zopts) zs, err := os.osCloud.ListDNSZones(zopts)

View File

@ -9,8 +9,6 @@ spec:
authorization: authorization:
alwaysAllow: {} alwaysAllow: {}
channel: stable channel: stable
cloudConfig:
openstack: {}
cloudProvider: openstack cloudProvider: openstack
configBase: memfs://tests/minimal-openstack.k8s.local configBase: memfs://tests/minimal-openstack.k8s.local
etcdClusters: etcdClusters:

View File

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCtWu40XQo8dczLsCq0OWV+hxm9uV3WxeH9Kgh4sMzQxNtoU1pvW0XdjpkBesRKGoolfWeCLXWxpyQb1IaiMkKoz7MdhQ/6UKjMjP66aFWWp3pwD0uj0HuJ7tq4gKHKRYGTaZIRWpzUiANBrjugVgA+Sd7E/mYwc/DMXkIyRZbvhQ==

View File

@ -0,0 +1,93 @@
apiVersion: kops.k8s.io/v1alpha2
kind: Cluster
metadata:
creationTimestamp: "2017-01-01T00:00:00Z"
name: floatingip-openstack.k8s.local
spec:
api:
dns: {}
authorization:
alwaysAllow: {}
channel: stable
cloudConfig:
openstack:
router:
externalNetwork: external
cloudProvider: openstack
configBase: memfs://tests/floatingip-openstack.k8s.local
etcdClusters:
- etcdMembers:
- instanceGroup: master-us-test1-a
name: "1"
volumeType: test
name: main
- etcdMembers:
- instanceGroup: master-us-test1-a
name: "1"
volumeType: test
name: events
openstackServiceAccount: default
iam:
legacy: false
kubelet:
anonymousAuth: false
kubernetesApiAccess:
- 0.0.0.0/0
kubernetesVersion: v1.16.0
masterPublicName: api.floatingip-openstack.k8s.local
networking:
kubenet: {}
networkCIDR: 192.168.0.0/16
nonMasqueradeCIDR: 100.64.0.0/10
project: testproject
sshAccess:
- 0.0.0.0/0
subnets:
- name: us-test1
region: us-test1
type: Public
topology:
dns:
type: Private
masters: private
nodes: private
---
apiVersion: kops.k8s.io/v1alpha2
kind: InstanceGroup
metadata:
creationTimestamp: "2017-01-01T00:00:00Z"
labels:
kops.k8s.io/cluster: floatingip-openstack.k8s.local
name: master-us-test1-a
spec:
image: Ubuntu-20.04
machineType: n1-standard-1
maxSize: 1
minSize: 1
role: Master
subnets:
- us-test1
zones:
- us-test1-a
---
apiVersion: kops.k8s.io/v1alpha2
kind: InstanceGroup
metadata:
creationTimestamp: "2017-01-01T00:00:00Z"
labels:
kops.k8s.io/cluster: floatingip-openstack.k8s.local
name: nodes
spec:
image: Ubuntu-20.04
machineType: n1-standard-2
maxSize: 2
minSize: 2
role: Node
subnets:
- us-test1
zones:
- us-test1-a

View File

@ -77,7 +77,7 @@ func (i *RouterInterface) Find(context *fi.Context) (*RouterInterface, error) {
for _, ip := range p.FixedIPs { for _, ip := range p.FixedIPs {
if ip.SubnetID == subnetID { if ip.SubnetID == subnetID {
if actual != nil { if actual != nil {
return nil, fmt.Errorf("find multiple interfaces which subnet:%s attach to", subnetID) return nil, fmt.Errorf("found multiple interfaces which subnet:%s attach to", subnetID)
} }
actual = &RouterInterface{ actual = &RouterInterface{
ID: fi.String(p.ID), ID: fi.String(p.ID),
@ -97,7 +97,7 @@ func (i *RouterInterface) Run(context *fi.Context) error {
return fi.DefaultDeltaRunMethod(i, context) return fi.DefaultDeltaRunMethod(i, context)
} }
func (_ *RouterInterface) CheckChanges(a, e, changes *RouterInterface) error { func (*RouterInterface) CheckChanges(a, e, changes *RouterInterface) error {
if a == nil { if a == nil {
if e.Router == nil { if e.Router == nil {
return fi.RequiredField("Router") return fi.RequiredField("Router")