mirror of https://github.com/kubernetes/kops.git
Merge pull request #9739 from olemarkus/openstack-floatingip-test
Add an integration test for openstack floating ip
This commit is contained in:
commit
c10d6c4710
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
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
|
||||||
|
|
|
@ -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] {
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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 {
|
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")
|
||||||
|
|
Loading…
Reference in New Issue