Add an integration test for openstack floating ip

* Integration test for floatingip cluster
* Implements mocking of floatingIP (only list for now)
* Expands various cloudmocks
* Fixes an NPR in openstack validation
* Fixes a bug where kops tries to use DNS even if the cluster is gossip
This commit is contained in:
Ole Markus With 2020-08-11 11:15:14 +02:00
parent 46ebae1b4e
commit 9890839cec
13 changed files with 236 additions and 10 deletions

View File

@ -4,6 +4,7 @@ go_library(
name = "go_default_library",
srcs = [
"api.go",
"floatingips.go",
"networks.go",
"ports.go",
"routers.go",
@ -16,6 +17,7 @@ go_library(
deps = [
"//cloudmock/openstack: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/security/groups: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"
"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/security/groups"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules"
@ -40,6 +42,7 @@ type MockClient struct {
securityGroups map[string]groups.SecGroup
securityGroupRules map[string]rules.SecGroupRule
subnets map[string]subnets.Subnet
floatingips map[string]floatingips.FloatingIP
}
// CreateClient will create a new mock networking client
@ -53,6 +56,7 @@ func CreateClient() *MockClient {
m.mockSecurityGroups()
m.mockSecurityGroupRules()
m.mockSubnets()
m.mockFloatingIPs()
m.Server = httptest.NewServer(m.Mux)
return m
}
@ -66,6 +70,7 @@ func (m *MockClient) Reset() {
m.securityGroups = make(map[string]groups.SecGroup)
m.securityGroupRules = make(map[string]rules.SecGroupRule)
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
@ -92,5 +97,8 @@ func (m *MockClient) All() map[string]interface{} {
for id, s := range m.subnets {
all[id] = s
}
for id, s := range m.floatingips {
all[id] = s
}
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)
nameFilter := vals.Get("name")
idFilter := vals.Get("id")
networkFilter := vals.Get("network_id")
deviceFilter := vals.Get("device_id")
for _, p := range m.ports {
if nameFilter != "" && nameFilter != p.Name {
continue
}
if deviceFilter != "" && deviceFilter != p.DeviceID {
continue
}
if networkFilter != "" && networkFilter != p.NetworkID {
continue
}
if idFilter != "" && idFilter != p.ID {
continue
}
ports = append(ports, p)
}
@ -152,6 +164,7 @@ func (m *MockClient) createPort(w http.ResponseWriter, r *http.Request) {
Name: create.Port.Name,
NetworkID: create.Port.NetworkID,
SecurityGroups: *create.Port.SecurityGroups,
DeviceID: create.Port.DeviceID,
FixedIPs: fixedIPs,
}
m.ports[p.ID] = p

View File

@ -24,6 +24,8 @@ import (
"regexp"
"strings"
"github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
"github.com/google/uuid"
"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)
nameFilter := vals.Get("name")
idFilter := vals.Get("id")
for _, r := range m.routers {
if nameFilter != "" && r.Name != nameFilter {
continue
}
if idFilter != "" && r.ID != idFilter {
continue
}
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")
}
if parts[2] == "add_router_interface" {
subnet := m.subnets[createInterface.SubnetID]
interfaces := m.routerInterfaces[routerID]
interfaces = append(interfaces, routers.InterfaceInfo{
SubnetID: createInterface.SubnetID,
SubnetID: subnet.ID,
})
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" {
interfaces := make([]routers.InterfaceInfo, 0)
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
func TestLifecyclePrivateCalico(t *testing.T) {
runLifecycleTestAWS(&LifecycleTestOptions{

View File

@ -22,6 +22,9 @@ import (
)
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 {
topology := c.Spec.Topology
if topology == nil || topology.Nodes == kops.TopologyPublic {

View File

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

View File

@ -19,6 +19,8 @@ package openstack
import (
"fmt"
"k8s.io/kops/pkg/dns"
"github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets"
"github.com/gophercloud/gophercloud/openstack/dns/v2/zones"
"k8s.io/kops/pkg/resources"
@ -31,13 +33,14 @@ const (
)
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)
if os.osCloud.DNSClient() == nil {
return nil, nil
zopts := zones.ListOpts{
Name: os.clusterName,
}
zs, err := os.osCloud.ListDNSZones(zopts)

View File

@ -9,8 +9,6 @@ spec:
authorization:
alwaysAllow: {}
channel: stable
cloudConfig:
openstack: {}
cloudProvider: openstack
configBase: memfs://tests/minimal-openstack.k8s.local
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 {
if ip.SubnetID == subnetID {
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{
ID: fi.String(p.ID),
@ -97,7 +97,7 @@ func (i *RouterInterface) Run(context *fi.Context) error {
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 e.Router == nil {
return fi.RequiredField("Router")