mirror of https://github.com/kubernetes/kops.git
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:
parent
46ebae1b4e
commit
9890839cec
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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] {
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -9,8 +9,6 @@ spec:
|
|||
authorization:
|
||||
alwaysAllow: {}
|
||||
channel: stable
|
||||
cloudConfig:
|
||||
openstack: {}
|
||||
cloudProvider: openstack
|
||||
configBase: memfs://tests/minimal-openstack.k8s.local
|
||||
etcdClusters:
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCtWu40XQo8dczLsCq0OWV+hxm9uV3WxeH9Kgh4sMzQxNtoU1pvW0XdjpkBesRKGoolfWeCLXWxpyQb1IaiMkKoz7MdhQ/6UKjMjP66aFWWp3pwD0uj0HuJ7tq4gKHKRYGTaZIRWpzUiANBrjugVgA+Sd7E/mYwc/DMXkIyRZbvhQ==
|
|
@ -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
|
|
@ -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")
|
||||
|
|
Loading…
Reference in New Issue