mirror of https://github.com/kubernetes/kops.git
660 lines
17 KiB
Go
660 lines
17 KiB
Go
/*
|
|
Copyright 2019 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 openstacktasks
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"sort"
|
|
"testing"
|
|
|
|
sg "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups"
|
|
"github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
|
|
"k8s.io/kops/upup/pkg/fi"
|
|
"k8s.io/kops/upup/pkg/fi/cloudup/openstack"
|
|
)
|
|
|
|
func Test_Port_GetDependencies(t *testing.T) {
|
|
tasks := map[string]fi.Task{
|
|
"foo": &SecurityGroup{Name: fi.String("security-group")},
|
|
"bar": &Subnet{Name: fi.String("subnet")},
|
|
"baz": &Instance{Name: fi.String("instance")},
|
|
"qux": &FloatingIP{Name: fi.String("fip")},
|
|
"xxx": &Network{Name: fi.String("network")},
|
|
}
|
|
|
|
port := &Port{}
|
|
|
|
actual := port.GetDependencies(tasks)
|
|
|
|
expected := []fi.Task{
|
|
&Subnet{Name: fi.String("subnet")},
|
|
&Network{Name: fi.String("network")},
|
|
&SecurityGroup{Name: fi.String("security-group")},
|
|
}
|
|
|
|
actualSorted := sortedTasks(actual)
|
|
expectedSorted := sortedTasks(expected)
|
|
sort.Sort(actualSorted)
|
|
sort.Sort(expectedSorted)
|
|
|
|
if !reflect.DeepEqual(expectedSorted, actualSorted) {
|
|
t.Errorf("Dependencies differ:\n%v\n\tinstead of\n%v", actualSorted, expectedSorted)
|
|
}
|
|
}
|
|
|
|
func Test_NewPortTaskFromCloud(t *testing.T) {
|
|
syncLifecycle := fi.LifecycleSync
|
|
tests := []struct {
|
|
desc string
|
|
lifecycle fi.Lifecycle
|
|
cloud openstack.OpenstackCloud
|
|
cloudPort *ports.Port
|
|
foundPort *Port
|
|
modifiedFoundPort *Port
|
|
expectedPortTask *Port
|
|
expectedError error
|
|
}{
|
|
{
|
|
desc: "empty cloud port found port nil",
|
|
lifecycle: fi.LifecycleSync,
|
|
cloud: &portCloud{},
|
|
cloudPort: &ports.Port{},
|
|
foundPort: nil,
|
|
modifiedFoundPort: nil,
|
|
expectedPortTask: &Port{
|
|
ID: fi.String(""),
|
|
Name: fi.String(""),
|
|
Network: &Network{ID: fi.String("")},
|
|
SecurityGroups: []*SecurityGroup{},
|
|
Subnets: []*Subnet{},
|
|
Lifecycle: &syncLifecycle,
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
desc: "empty cloud port found port not nil",
|
|
lifecycle: fi.LifecycleSync,
|
|
cloud: &portCloud{},
|
|
cloudPort: &ports.Port{},
|
|
foundPort: &Port{},
|
|
modifiedFoundPort: &Port{ID: fi.String("")},
|
|
expectedPortTask: &Port{
|
|
ID: fi.String(""),
|
|
Name: fi.String(""),
|
|
Network: &Network{ID: fi.String("")},
|
|
SecurityGroups: []*SecurityGroup{},
|
|
Subnets: []*Subnet{},
|
|
Lifecycle: &syncLifecycle,
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
desc: "fully populated cloud port found port not nil",
|
|
lifecycle: fi.LifecycleSync,
|
|
cloud: &portCloud{},
|
|
cloudPort: &ports.Port{
|
|
ID: "id",
|
|
Name: "name",
|
|
NetworkID: "networkID",
|
|
FixedIPs: []ports.IP{
|
|
{SubnetID: "subnet-a"},
|
|
{SubnetID: "subnet-b"},
|
|
},
|
|
SecurityGroups: []string{
|
|
"sg-1",
|
|
"sg-2",
|
|
},
|
|
},
|
|
foundPort: &Port{},
|
|
modifiedFoundPort: &Port{ID: fi.String("id")},
|
|
expectedPortTask: &Port{
|
|
ID: fi.String("id"),
|
|
Name: fi.String("name"),
|
|
Network: &Network{ID: fi.String("networkID")},
|
|
SecurityGroups: []*SecurityGroup{
|
|
{ID: fi.String("sg-1"), Lifecycle: &syncLifecycle},
|
|
{ID: fi.String("sg-2"), Lifecycle: &syncLifecycle},
|
|
},
|
|
Subnets: []*Subnet{
|
|
{ID: fi.String("subnet-a"), Lifecycle: &syncLifecycle},
|
|
{ID: fi.String("subnet-b"), Lifecycle: &syncLifecycle},
|
|
},
|
|
Lifecycle: &syncLifecycle,
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
desc: "cloud port found port not nil honors additional security groups",
|
|
lifecycle: fi.LifecycleSync,
|
|
cloud: &portCloud{
|
|
listSecurityGroups: map[string][]sg.SecGroup{
|
|
"add-1": {
|
|
{ID: "add-1", Name: "add-1"},
|
|
},
|
|
"add-2": {
|
|
{ID: "add-2", Name: "add-2"},
|
|
},
|
|
},
|
|
},
|
|
cloudPort: &ports.Port{
|
|
ID: "id",
|
|
Name: "name",
|
|
NetworkID: "networkID",
|
|
FixedIPs: []ports.IP{
|
|
{SubnetID: "subnet-a"},
|
|
{SubnetID: "subnet-b"},
|
|
},
|
|
SecurityGroups: []string{
|
|
"sg-1",
|
|
"sg-2",
|
|
"add-1",
|
|
"add-2",
|
|
},
|
|
},
|
|
foundPort: &Port{
|
|
AdditionalSecurityGroups: []string{
|
|
"add-1",
|
|
"add-2",
|
|
},
|
|
},
|
|
modifiedFoundPort: &Port{
|
|
ID: fi.String("id"),
|
|
AdditionalSecurityGroups: []string{
|
|
"add-1",
|
|
"add-2",
|
|
},
|
|
},
|
|
expectedPortTask: &Port{
|
|
ID: fi.String("id"),
|
|
Name: fi.String("name"),
|
|
Network: &Network{ID: fi.String("networkID")},
|
|
SecurityGroups: []*SecurityGroup{
|
|
{ID: fi.String("sg-1"), Lifecycle: &syncLifecycle},
|
|
{ID: fi.String("sg-2"), Lifecycle: &syncLifecycle},
|
|
},
|
|
AdditionalSecurityGroups: []string{
|
|
"add-1",
|
|
"add-2",
|
|
},
|
|
Subnets: []*Subnet{
|
|
{ID: fi.String("subnet-a"), Lifecycle: &syncLifecycle},
|
|
{ID: fi.String("subnet-b"), Lifecycle: &syncLifecycle},
|
|
},
|
|
Lifecycle: &syncLifecycle,
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
}
|
|
|
|
for _, testCase := range tests {
|
|
t.Run(testCase.desc, func(t *testing.T) {
|
|
actual, err := NewPortTaskFromCloud(testCase.cloud, &testCase.lifecycle, testCase.cloudPort, testCase.foundPort)
|
|
|
|
if !reflect.DeepEqual(err, testCase.expectedError) {
|
|
t.Errorf("Error differs:\n%v\n\tinstead of\n%v", err, testCase.expectedError)
|
|
}
|
|
|
|
if !reflect.DeepEqual(actual, testCase.expectedPortTask) {
|
|
t.Errorf("Port task differs:\n%v\n\tinstead of\n%v", actual, testCase.expectedPortTask)
|
|
}
|
|
|
|
if !reflect.DeepEqual(testCase.foundPort, testCase.modifiedFoundPort) {
|
|
t.Errorf("Found Port task differs:\n%v\n\tinstead of\n%v", testCase.foundPort, testCase.modifiedFoundPort)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_Port_Find(t *testing.T) {
|
|
syncLifecycle := fi.LifecycleSync
|
|
tests := []struct {
|
|
desc string
|
|
context *fi.Context
|
|
port *Port
|
|
expectedPortTask *Port
|
|
expectedError error
|
|
}{
|
|
{
|
|
desc: "nothing found",
|
|
context: &fi.Context{
|
|
Cloud: &portCloud{},
|
|
},
|
|
port: &Port{
|
|
Name: fi.String("name"),
|
|
Lifecycle: &syncLifecycle,
|
|
},
|
|
expectedPortTask: nil,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
desc: "port found",
|
|
context: &fi.Context{
|
|
Cloud: &portCloud{
|
|
listPorts: []ports.Port{
|
|
{
|
|
ID: "id",
|
|
Name: "name",
|
|
NetworkID: "networkID",
|
|
FixedIPs: []ports.IP{
|
|
{SubnetID: "subnet-a"},
|
|
{SubnetID: "subnet-b"},
|
|
},
|
|
SecurityGroups: []string{
|
|
"sg-1",
|
|
"sg-2",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
port: &Port{
|
|
Name: fi.String("name"),
|
|
Lifecycle: &syncLifecycle,
|
|
},
|
|
expectedPortTask: &Port{
|
|
ID: fi.String("id"),
|
|
Name: fi.String("name"),
|
|
Network: &Network{ID: fi.String("networkID")},
|
|
SecurityGroups: []*SecurityGroup{
|
|
{ID: fi.String("sg-1"), Lifecycle: &syncLifecycle},
|
|
{ID: fi.String("sg-2"), Lifecycle: &syncLifecycle},
|
|
},
|
|
Subnets: []*Subnet{
|
|
{ID: fi.String("subnet-a"), Lifecycle: &syncLifecycle},
|
|
{ID: fi.String("subnet-b"), Lifecycle: &syncLifecycle},
|
|
},
|
|
Lifecycle: &syncLifecycle,
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
desc: "multiple ports found",
|
|
context: &fi.Context{
|
|
Cloud: &portCloud{
|
|
listPorts: []ports.Port{
|
|
{
|
|
ID: "id-1",
|
|
Name: "name",
|
|
},
|
|
{
|
|
ID: "id-2",
|
|
Name: "name",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
port: &Port{
|
|
Name: fi.String("name"),
|
|
Lifecycle: &syncLifecycle,
|
|
},
|
|
expectedPortTask: nil,
|
|
expectedError: fmt.Errorf("found multiple ports with name: name"),
|
|
},
|
|
{
|
|
desc: "error listing ports",
|
|
context: &fi.Context{
|
|
Cloud: &portCloud{
|
|
listPorts: []ports.Port{
|
|
{
|
|
ID: "id-1",
|
|
Name: "name",
|
|
},
|
|
},
|
|
listPortsError: fmt.Errorf("list error"),
|
|
},
|
|
},
|
|
port: &Port{
|
|
Name: fi.String("name"),
|
|
Lifecycle: &syncLifecycle,
|
|
},
|
|
expectedPortTask: nil,
|
|
expectedError: fmt.Errorf("list error"),
|
|
},
|
|
}
|
|
|
|
for _, testCase := range tests {
|
|
t.Run(testCase.desc, func(t *testing.T) {
|
|
actual, err := testCase.port.Find(testCase.context)
|
|
|
|
if !reflect.DeepEqual(err, testCase.expectedError) {
|
|
t.Errorf("Error differs:\n%v\n\tinstead of\n%v", err, testCase.expectedError)
|
|
}
|
|
|
|
if !reflect.DeepEqual(actual, testCase.expectedPortTask) {
|
|
t.Errorf("Port task differs:\n%v\n\tinstead of\n%v", actual, testCase.expectedPortTask)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_Port_CheckChanges(t *testing.T) {
|
|
tests := []struct {
|
|
desc string
|
|
actual *Port
|
|
expected *Port
|
|
changes *Port
|
|
expectedError error
|
|
}{
|
|
{
|
|
desc: "actual nil all required fields set",
|
|
actual: nil,
|
|
expected: &Port{
|
|
Name: fi.String("name"),
|
|
Network: &Network{ID: fi.String("networkID")},
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
desc: "actual nil required field Name nil",
|
|
actual: nil,
|
|
expected: &Port{
|
|
Name: nil,
|
|
Network: &Network{ID: fi.String("networkID")},
|
|
},
|
|
expectedError: fi.RequiredField("Name"),
|
|
},
|
|
{
|
|
desc: "actual nil required field Network nil",
|
|
actual: nil,
|
|
expected: &Port{
|
|
Name: fi.String("name"),
|
|
Network: nil,
|
|
},
|
|
expectedError: fi.RequiredField("Network"),
|
|
},
|
|
{
|
|
desc: "actual not nil all changeable fields set",
|
|
actual: &Port{
|
|
Name: fi.String("name"),
|
|
Network: nil,
|
|
},
|
|
expected: &Port{
|
|
Name: fi.String("name"),
|
|
Network: nil,
|
|
},
|
|
changes: &Port{
|
|
Name: nil,
|
|
Network: &Network{ID: fi.String("networkID")},
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
desc: "actual not nil unchangeable field Name set",
|
|
actual: &Port{
|
|
Name: fi.String("name"),
|
|
Network: nil,
|
|
},
|
|
expected: &Port{
|
|
Name: fi.String("name"),
|
|
Network: &Network{ID: fi.String("networkID")},
|
|
},
|
|
changes: &Port{
|
|
Name: fi.String("name"),
|
|
Network: nil,
|
|
},
|
|
expectedError: fi.CannotChangeField("Name"),
|
|
},
|
|
{
|
|
desc: "actual not nil unchangeable field Network set",
|
|
actual: &Port{
|
|
Name: fi.String("name"),
|
|
Network: nil,
|
|
},
|
|
expected: &Port{
|
|
Name: nil,
|
|
Network: &Network{ID: fi.String("networkID")},
|
|
},
|
|
changes: &Port{
|
|
Name: nil,
|
|
Network: &Network{ID: fi.String("networkID")},
|
|
},
|
|
expectedError: fi.CannotChangeField("Network"),
|
|
},
|
|
}
|
|
|
|
for _, testCase := range tests {
|
|
t.Run(testCase.desc, func(t *testing.T) {
|
|
var port Port
|
|
err := (&port).CheckChanges(testCase.actual, testCase.expected, testCase.changes)
|
|
|
|
if !reflect.DeepEqual(err, testCase.expectedError) {
|
|
t.Errorf("Error differs:\n%v\n\tinstead of\n%v", err, testCase.expectedError)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_Port_RenderOpenstack(t *testing.T) {
|
|
tests := []struct {
|
|
desc string
|
|
target *openstack.OpenstackAPITarget
|
|
actual *Port
|
|
expected *Port
|
|
changes *Port
|
|
expectedCloudPort *ports.Port
|
|
expectedAfter *Port
|
|
expectedError error
|
|
}{
|
|
{
|
|
desc: "actual not nil",
|
|
actual: &Port{
|
|
ID: fi.String("actual-id"),
|
|
Name: fi.String("name"),
|
|
Network: &Network{ID: fi.String("networkID")},
|
|
},
|
|
expected: &Port{
|
|
ID: fi.String("expected-id"),
|
|
Name: fi.String("name"),
|
|
Network: &Network{ID: fi.String("networkID")},
|
|
},
|
|
expectedAfter: &Port{
|
|
ID: fi.String("actual-id"),
|
|
Name: fi.String("name"),
|
|
Network: &Network{ID: fi.String("networkID")},
|
|
},
|
|
expectedCloudPort: nil,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
desc: "actual nil success",
|
|
target: &openstack.OpenstackAPITarget{
|
|
Cloud: &portCloud{
|
|
createPort: &ports.Port{
|
|
ID: "cloud-id",
|
|
Name: "name",
|
|
NetworkID: "networkID",
|
|
FixedIPs: []ports.IP{
|
|
{SubnetID: "subnet-a"},
|
|
{SubnetID: "subnet-b"},
|
|
},
|
|
SecurityGroups: []string{
|
|
"sg-1",
|
|
"sg-2",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
actual: nil,
|
|
expected: &Port{
|
|
ID: fi.String("expected-id"),
|
|
Name: fi.String("name"),
|
|
Network: &Network{ID: fi.String("networkID")},
|
|
SecurityGroups: []*SecurityGroup{
|
|
{ID: fi.String("sg-1")},
|
|
{ID: fi.String("sg-2")},
|
|
},
|
|
Subnets: []*Subnet{
|
|
{ID: fi.String("subnet-a")},
|
|
{ID: fi.String("subnet-b")},
|
|
},
|
|
},
|
|
expectedAfter: &Port{
|
|
ID: fi.String("cloud-id"),
|
|
Name: fi.String("name"),
|
|
Network: &Network{ID: fi.String("networkID")},
|
|
SecurityGroups: []*SecurityGroup{
|
|
{ID: fi.String("sg-1")},
|
|
{ID: fi.String("sg-2")},
|
|
},
|
|
Subnets: []*Subnet{
|
|
{ID: fi.String("subnet-a")},
|
|
{ID: fi.String("subnet-b")},
|
|
},
|
|
},
|
|
expectedCloudPort: &ports.Port{
|
|
ID: "id",
|
|
Name: "name",
|
|
NetworkID: "networkID",
|
|
FixedIPs: []ports.IP{
|
|
{SubnetID: "subnet-a"},
|
|
{SubnetID: "subnet-b"},
|
|
},
|
|
SecurityGroups: []string{
|
|
"sg-1",
|
|
"sg-2",
|
|
},
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
desc: "actual nil cloud error",
|
|
target: &openstack.OpenstackAPITarget{
|
|
Cloud: &portCloud{
|
|
createPortError: fmt.Errorf("port create error"),
|
|
},
|
|
},
|
|
actual: nil,
|
|
expected: &Port{
|
|
ID: fi.String("expected-id"),
|
|
Name: fi.String("name"),
|
|
Network: &Network{ID: fi.String("networkID")},
|
|
},
|
|
expectedAfter: &Port{
|
|
ID: fi.String("expected-id"),
|
|
Name: fi.String("name"),
|
|
Network: &Network{ID: fi.String("networkID")},
|
|
},
|
|
expectedCloudPort: nil,
|
|
expectedError: fmt.Errorf("Error creating port: port create error"),
|
|
},
|
|
}
|
|
|
|
for _, testCase := range tests {
|
|
t.Run(testCase.desc, func(t *testing.T) {
|
|
var port Port
|
|
err := (&port).RenderOpenstack(testCase.target, testCase.actual, testCase.expected, testCase.changes)
|
|
|
|
if !reflect.DeepEqual(err, testCase.expectedError) {
|
|
t.Errorf("Error differs:\n%v\n\tinstead of\n%v", err, testCase.expectedError)
|
|
}
|
|
|
|
if !reflect.DeepEqual(testCase.expected, testCase.expectedAfter) {
|
|
t.Errorf("Expected Port task differs:\n%v\n\tinstead of\n%v", testCase.expected, testCase.expectedAfter)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_Port_createOptsFromPortTask(t *testing.T) {
|
|
tests := []struct {
|
|
desc string
|
|
actual *Port
|
|
expected *Port
|
|
changes *Port
|
|
expectedCreateOpts ports.CreateOptsBuilder
|
|
expectedError error
|
|
}{
|
|
{
|
|
desc: "all fields set",
|
|
expected: &Port{
|
|
ID: fi.String("expected-id"),
|
|
Name: fi.String("name"),
|
|
Network: &Network{ID: fi.String("networkID")},
|
|
SecurityGroups: []*SecurityGroup{
|
|
{ID: fi.String("sg-1")},
|
|
{ID: fi.String("sg-2")},
|
|
},
|
|
AdditionalSecurityGroups: []string{
|
|
"add-1",
|
|
"add-2",
|
|
},
|
|
Subnets: []*Subnet{
|
|
{ID: fi.String("subnet-a")},
|
|
{ID: fi.String("subnet-b")},
|
|
},
|
|
},
|
|
expectedCreateOpts: ports.CreateOpts{
|
|
Name: "name",
|
|
NetworkID: "networkID",
|
|
SecurityGroups: &[]string{
|
|
"sg-1",
|
|
"sg-2",
|
|
"add-1",
|
|
"add-2",
|
|
},
|
|
FixedIPs: []ports.IP{
|
|
{SubnetID: "subnet-a"},
|
|
{SubnetID: "subnet-b"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, testCase := range tests {
|
|
t.Run(testCase.desc, func(t *testing.T) {
|
|
opts, err := portCreateOptsFromPortTask(testCase.actual, testCase.expected, testCase.changes)
|
|
|
|
if !reflect.DeepEqual(err, testCase.expectedError) {
|
|
t.Errorf("Error differs:\n%v\n\tinstead of\n%v", err, testCase.expectedError)
|
|
}
|
|
|
|
if !reflect.DeepEqual(testCase.expectedCreateOpts, opts) {
|
|
t.Errorf("Port create opts differs:\n%v\n\tinstead of\n%v", opts, testCase.expectedCreateOpts)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
type portCloud struct {
|
|
openstack.OpenstackCloud
|
|
listPorts []ports.Port
|
|
listPortsError error
|
|
createPort *ports.Port
|
|
createPortError error
|
|
listSecurityGroups map[string][]sg.SecGroup
|
|
listSecurityGroupsError error
|
|
}
|
|
|
|
func (p *portCloud) ListPorts(opt ports.ListOptsBuilder) ([]ports.Port, error) {
|
|
return p.listPorts, p.listPortsError
|
|
}
|
|
|
|
func (p *portCloud) CreatePort(opt ports.CreateOptsBuilder) (*ports.Port, error) {
|
|
return p.createPort, p.createPortError
|
|
}
|
|
|
|
func (p *portCloud) ListSecurityGroups(opt sg.ListOpts) ([]sg.SecGroup, error) {
|
|
return p.listSecurityGroups[opt.Name], p.listSecurityGroupsError
|
|
}
|
|
|
|
type sortedTasks []fi.Task
|
|
|
|
func (s sortedTasks) Len() int { return len(s) }
|
|
func (s sortedTasks) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
|
func (s sortedTasks) Less(i, j int) bool { return fmt.Sprintf("%v", s[i]) < fmt.Sprintf("%v", s[j]) }
|