diff --git a/tests/functional/api_fixture.go b/tests/functional/api_fixture.go index 66019a6a..f33bc7c5 100644 --- a/tests/functional/api_fixture.go +++ b/tests/functional/api_fixture.go @@ -27,6 +27,7 @@ import ( . "github.com/onsi/ginkgo/v2" //revive:disable:dot-imports "github.com/gophercloud/gophercloud/openstack/identity/v3/projects" + "github.com/gophercloud/gophercloud/openstack/identity/v3/users" keystone_helpers "github.com/openstack-k8s-operators/keystone-operator/api/test/helpers" api "github.com/openstack-k8s-operators/lib-common/modules/test/apis" @@ -172,11 +173,13 @@ func keystoneGetProject( f.APIFixture.Log.Info(fmt.Sprintf("GetProject returns %s", string(bytes))) } -func SetupAPIFixtures(logger logr.Logger) ( - *keystone_helpers.KeystoneAPIFixture, - *NovaAPIFixture, - *NeutronAPIFixture, -) { +type APIFixtures struct { + Keystone *keystone_helpers.KeystoneAPIFixture + Nova *NovaAPIFixture + Neutron *NeutronAPIFixture +} + +func SetupAPIFixtures(logger logr.Logger) APIFixtures { nova := NewNovaAPIFixtureWithServer(logger) nova.Setup() DeferCleanup(nova.Cleanup) @@ -188,6 +191,12 @@ func SetupAPIFixtures(logger logr.Logger) ( neutronURL := neutron.Endpoint() keystone := keystone_helpers.NewKeystoneAPIFixtureWithServer(logger) + keystone.Users = map[string]users.User{ + "octavia": { + Name: "octavia", + ID: uuid.New().String(), + }, + } keystone.Setup( api.Handler{Pattern: "/", Func: keystone.HandleVersion}, api.Handler{Pattern: "/v3/users", Func: keystone.HandleUsers}, @@ -205,5 +214,9 @@ func SetupAPIFixtures(logger logr.Logger) ( } }}) DeferCleanup(keystone.Cleanup) - return keystone, nova, neutron + return APIFixtures{ + Keystone: keystone, + Nova: nova, + Neutron: neutron, + } } diff --git a/tests/functional/base_test.go b/tests/functional/base_test.go index 31c4022f..5776804f 100644 --- a/tests/functional/base_test.go +++ b/tests/functional/base_test.go @@ -17,12 +17,14 @@ limitations under the License. package functional_test import ( + "encoding/json" "fmt" "time" . "github.com/onsi/ginkgo/v2" //revive:disable:dot-imports . "github.com/onsi/gomega" //revive:disable:dot-imports + networkv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -32,6 +34,7 @@ import ( condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" "github.com/openstack-k8s-operators/lib-common/modules/common/endpoint" octaviav1 "github.com/openstack-k8s-operators/octavia-operator/api/v1beta1" + "github.com/openstack-k8s-operators/octavia-operator/pkg/octavia" ) const ( @@ -106,8 +109,14 @@ func SimulateKeystoneReady( func GetDefaultOctaviaSpec() map[string]interface{} { return map[string]interface{}{ - "databaseInstance": "test-octavia-db-instance", - "secret": SecretName, + "databaseInstance": "test-octavia-db-instance", + "secret": SecretName, + "octaviaNetworkAttachment": "octavia-attachement", + "databaseAccount": "octavia-db-account", + "persistenceDatabaseAccount": "octavia-persistence-db-account", + "lbMgmtNetwork": map[string]interface{}{ + "availabilityZones": []string{"az0"}, + }, } } @@ -215,3 +224,81 @@ func OctaviaAPIConditionGetter(name types.NamespacedName) condition.Conditions { instance := GetOctaviaAPI(name) return instance.Status.Conditions } + +func SimulateOctaviaAPIReady(name types.NamespacedName) { + Eventually(func(g Gomega) { + octaviaAPI := GetOctaviaAPI(name) + octaviaAPI.Status.ObservedGeneration = octaviaAPI.Generation + octaviaAPI.Status.ReadyCount = 1 + g.Expect(k8sClient.Status().Update(ctx, octaviaAPI)).To(Succeed()) + }, timeout, interval).Should(Succeed()) +} + +func CreateNAD(name types.NamespacedName) client.Object { + raw := map[string]interface{}{ + "apiVersion": "k8s.cni.cncf.io/v1", + "kind": "NetworkAttachmentDefinition", + "metadata": map[string]interface{}{ + "name": name.Name, + "namespace": name.Namespace, + }, + "spec": map[string]interface{}{ + "config": `{ + "cniVersion": "0.3.1", + "name": "octavia", + "type": "bridge", + "bridge": "octbr", + "ipam": { + "type": "whereabouts", + "range": "172.23.0.0/24", + "range_start": "172.23.0.30", + "range_end": "172.23.0.70", + "routes": [{ + "dst": "172.24.0.0/16", + "gw" : "172.23.0.150" + }] + } + }`, + }, + } + return th.CreateUnstructured(raw) +} + +func GetNADConfig(name types.NamespacedName) *octavia.NADConfig { + nad := &networkv1.NetworkAttachmentDefinition{} + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, name, nad)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + + nadConfig := &octavia.NADConfig{} + jsonDoc := []byte(nad.Spec.Config) + err := json.Unmarshal(jsonDoc, nadConfig) + if err != nil { + return nil + } + + return nadConfig +} + +func CreateNode(name types.NamespacedName) client.Object { + raw := map[string]interface{}{ + "apiVersion": "v1", + "kind": "Node", + "metadata": map[string]interface{}{ + "name": name.Name, + "namespace": name.Namespace, + }, + "spec": map[string]interface{}{}, + } + return th.CreateUnstructured(raw) + +} + +func CreateSSHPubKey() client.Object { + return th.CreateConfigMap(types.NamespacedName{ + Name: "octavia-ssh-pubkey", + Namespace: namespace, + }, map[string]interface{}{ + "key": []byte("public key"), + }) +} diff --git a/tests/functional/neutron_api_fixture.go b/tests/functional/neutron_api_fixture.go index e7bf455e..5ca4300b 100644 --- a/tests/functional/neutron_api_fixture.go +++ b/tests/functional/neutron_api_fixture.go @@ -24,15 +24,27 @@ import ( "strings" "github.com/go-logr/logr" + "github.com/google/uuid" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/quotas" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups" + "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" + "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" + "github.com/gophercloud/gophercloud/openstack/networking/v2/subnets" api "github.com/openstack-k8s-operators/lib-common/modules/test/apis" ) type NeutronAPIFixture struct { api.APIFixture - Quotas map[string]quotas.Quota - DefaultQuota quotas.Quota + Quotas map[string]quotas.Quota + DefaultQuota quotas.Quota + Networks map[string]networks.Network + Subnets map[string]subnets.Subnet + SecGroups map[string]groups.SecGroup + Ports map[string]ports.Port + Routers map[string]routers.Router + InterfaceInfos map[string]routers.InterfaceInfo } func (f *NeutronAPIFixture) registerHandler(handler api.Handler) { @@ -40,9 +52,517 @@ func (f *NeutronAPIFixture) registerHandler(handler api.Handler) { } func (f *NeutronAPIFixture) Setup() { + f.registerHandler(api.Handler{Pattern: "/v2.0/networks/", Func: f.networkHandler}) + f.registerHandler(api.Handler{Pattern: "/v2.0/networks", Func: f.networkHandler}) + f.registerHandler(api.Handler{Pattern: "/v2.0/subnets/", Func: f.subnetHandler}) + f.registerHandler(api.Handler{Pattern: "/v2.0/subnets", Func: f.subnetHandler}) + f.registerHandler(api.Handler{Pattern: "/v2.0/security-groups/", Func: f.securityGroupHandler}) + f.registerHandler(api.Handler{Pattern: "/v2.0/security-groups", Func: f.securityGroupHandler}) + f.registerHandler(api.Handler{Pattern: "/v2.0/ports/", Func: f.portHandler}) + f.registerHandler(api.Handler{Pattern: "/v2.0/ports", Func: f.portHandler}) + f.registerHandler(api.Handler{Pattern: "/v2.0/routers/", Func: f.routerHandler}) + f.registerHandler(api.Handler{Pattern: "/v2.0/routers", Func: f.routerHandler}) f.registerHandler(api.Handler{Pattern: "/v2.0/quotas/", Func: f.quotasHandler}) } +// Network +func (f *NeutronAPIFixture) networkHandler(w http.ResponseWriter, r *http.Request) { + f.LogRequest(r) + switch r.Method { + case "GET": + f.getNetwork(w, r) + case "POST": + f.postNetwork(w, r) + default: + f.UnexpectedRequest(w, r) + return + } +} + +func (f *NeutronAPIFixture) getNetwork(w http.ResponseWriter, r *http.Request) { + items := strings.Split(r.URL.Path, "/") + if len(items) == 4 { + var n struct { + Networks []networks.Network `json:"networks"` + } + n.Networks = []networks.Network{} + query := r.URL.Query() + name := query["name"] + tenantID := query["tenant_id"] + for _, network := range f.Networks { + if len(name) > 0 && name[0] != network.Name { + continue + } + if len(tenantID) > 0 && tenantID[0] != network.TenantID { + continue + } + n.Networks = append(n.Networks, network) + } + bytes, err := json.Marshal(&n) + if err != nil { + f.InternalError(err, "Error during marshalling response", w, r) + return + } + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(200) + fmt.Fprint(w, string(bytes)) + } +} + +func (f *NeutronAPIFixture) postNetwork(w http.ResponseWriter, r *http.Request) { + bytes, err := io.ReadAll(r.Body) + if err != nil { + f.InternalError(err, "Error reading request body", w, r) + return + } + + var n struct { + Network networks.Network `json:"network"` + } + + err = json.Unmarshal(bytes, &n) + if err != nil { + f.InternalError(err, "Error during marshalling response", w, r) + return + } + + networkID := uuid.New().String() + n.Network.ID = networkID + f.Networks[networkID] = n.Network + + bytes, err = json.Marshal(&n) + if err != nil { + f.InternalError(err, "Error during marshalling response", w, r) + return + } + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(201) + fmt.Fprint(w, string(bytes)) +} + +// Subnet +func (f *NeutronAPIFixture) subnetHandler(w http.ResponseWriter, r *http.Request) { + f.LogRequest(r) + switch r.Method { + case "GET": + f.getSubnet(w, r) + case "POST": + f.postSubnet(w, r) + case "PUT": + f.putSubnet(w, r) + default: + f.UnexpectedRequest(w, r) + return + } +} + +func (f *NeutronAPIFixture) getSubnet(w http.ResponseWriter, r *http.Request) { + items := strings.Split(r.URL.Path, "/") + if len(items) == 4 { + var n struct { + Subnets []subnets.Subnet `json:"subnets"` + } + n.Subnets = []subnets.Subnet{} + query := r.URL.Query() + name := query["name"] + tenantID := query["tenant_id"] + networkID := query["network_id"] + ipVersion := query["ip_version"] + for _, subnet := range f.Subnets { + if len(name) > 0 && name[0] != subnet.Name { + continue + } + if len(tenantID) > 0 && tenantID[0] != subnet.TenantID { + continue + } + if len(networkID) > 0 && networkID[0] != subnet.NetworkID { + continue + } + if len(ipVersion) > 0 && ipVersion[0] != fmt.Sprintf("%d", subnet.IPVersion) { + continue + } + n.Subnets = append(n.Subnets, subnet) + } + bytes, err := json.Marshal(&n) + if err != nil { + f.InternalError(err, "Error during marshalling response", w, r) + return + } + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(200) + fmt.Fprint(w, string(bytes)) + } +} + +func (f *NeutronAPIFixture) postSubnet(w http.ResponseWriter, r *http.Request) { + bytes, err := io.ReadAll(r.Body) + if err != nil { + f.InternalError(err, "Error reading request body", w, r) + return + } + + var n struct { + Subnet subnets.Subnet `json:"subnet"` + } + + err = json.Unmarshal(bytes, &n) + if err != nil { + f.InternalError(err, "Error during marshalling response", w, r) + return + } + + networkID := n.Subnet.NetworkID + subnetID := uuid.New().String() + n.Subnet.ID = subnetID + f.Subnets[subnetID] = n.Subnet + + network := f.Networks[networkID] + network.Subnets = append(network.Subnets, subnetID) + f.Networks[networkID] = network + + bytes, err = json.Marshal(&n) + if err != nil { + f.InternalError(err, "Error during marshalling response", w, r) + return + } + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(201) + fmt.Fprint(w, string(bytes)) +} + +func (f *NeutronAPIFixture) putSubnet(w http.ResponseWriter, r *http.Request) { + items := strings.Split(r.URL.Path, "/") + subnetID := items[4] + + bytes, err := io.ReadAll(r.Body) + if err != nil { + f.InternalError(err, "Error reading request body", w, r) + return + } + + var n struct { + Subnet subnets.Subnet `json:"subnet"` + } + + err = json.Unmarshal(bytes, &n) + if err != nil { + f.InternalError(err, "Error during marshalling response", w, r) + return + } + + subnet := f.Subnets[subnetID] + if len(n.Subnet.HostRoutes) > 0 { + subnet.HostRoutes = n.Subnet.HostRoutes + } + f.Subnets[subnetID] = subnet + + bytes, err = json.Marshal(&n) + if err != nil { + f.InternalError(err, "Error during marshalling response", w, r) + return + } + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(201) + fmt.Fprint(w, string(bytes)) +} + +// SecGroup +func (f *NeutronAPIFixture) securityGroupHandler(w http.ResponseWriter, r *http.Request) { + f.LogRequest(r) + switch r.Method { + case "GET": + f.getSecurityGroup(w, r) + case "POST": + f.postSecurityGroup(w, r) + default: + f.UnexpectedRequest(w, r) + return + } +} + +func (f *NeutronAPIFixture) getSecurityGroup(w http.ResponseWriter, r *http.Request) { + items := strings.Split(r.URL.Path, "/") + if len(items) == 4 { + var n struct { + SecGroups []groups.SecGroup `json:"security_groups"` + } + n.SecGroups = []groups.SecGroup{} + query := r.URL.Query() + name := query["name"] + tenantID := query["tenant_id"] + for _, securityGroup := range f.SecGroups { + if len(name) > 0 && name[0] != securityGroup.Name { + continue + } + if len(tenantID) > 0 && tenantID[0] != securityGroup.TenantID { + continue + } + n.SecGroups = append(n.SecGroups, securityGroup) + } + bytes, err := json.Marshal(&n) + if err != nil { + f.InternalError(err, "Error during marshalling response", w, r) + return + } + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(200) + fmt.Fprint(w, string(bytes)) + } +} + +func (f *NeutronAPIFixture) postSecurityGroup(w http.ResponseWriter, r *http.Request) { + bytes, err := io.ReadAll(r.Body) + if err != nil { + f.InternalError(err, "Error reading request body", w, r) + return + } + + var n struct { + SecGroup groups.SecGroup `json:"security_group"` + } + + err = json.Unmarshal(bytes, &n) + if err != nil { + f.InternalError(err, "Error during marshalling response", w, r) + return + } + + securityGroupID := uuid.New().String() + n.SecGroup.ID = securityGroupID + f.SecGroups[securityGroupID] = n.SecGroup + + bytes, err = json.Marshal(&n) + if err != nil { + f.InternalError(err, "Error during marshalling response", w, r) + return + } + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(201) + fmt.Fprint(w, string(bytes)) +} + +// Port +func (f *NeutronAPIFixture) portHandler(w http.ResponseWriter, r *http.Request) { + f.LogRequest(r) + switch r.Method { + case "GET": + f.getPort(w, r) + case "POST": + f.postPort(w, r) + default: + f.UnexpectedRequest(w, r) + return + } +} + +func (f *NeutronAPIFixture) getPort(w http.ResponseWriter, r *http.Request) { + items := strings.Split(r.URL.Path, "/") + if len(items) == 4 { + var n struct { + Ports []ports.Port `json:"ports"` + } + n.Ports = []ports.Port{} + query := r.URL.Query() + name := query["name"] + tenantID := query["tenant_id"] + networkID := query["network_id"] + for _, port := range f.Ports { + if len(name) > 0 && name[0] != port.Name { + continue + } + if len(tenantID) > 0 && tenantID[0] != port.TenantID { + continue + } + if len(networkID) > 0 && networkID[0] != port.NetworkID { + continue + } + n.Ports = append(n.Ports, port) + } + bytes, err := json.Marshal(&n) + if err != nil { + f.InternalError(err, "Error during marshalling response", w, r) + return + } + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(200) + fmt.Fprint(w, string(bytes)) + } +} + +func (f *NeutronAPIFixture) postPort(w http.ResponseWriter, r *http.Request) { + bytes, err := io.ReadAll(r.Body) + if err != nil { + f.InternalError(err, "Error reading request body", w, r) + return + } + + var n struct { + Port ports.Port `json:"port"` + } + + err = json.Unmarshal(bytes, &n) + if err != nil { + f.InternalError(err, "Error during marshalling response", w, r) + return + } + + network := f.Networks[n.Port.NetworkID] + + portID := uuid.New().String() + n.Port.ID = portID + n.Port.FixedIPs = []ports.IP{ + { + IPAddress: fmt.Sprintf("%s.ipaddress", portID), + SubnetID: network.Subnets[0], + }, + } + f.Ports[portID] = n.Port + + bytes, err = json.Marshal(&n) + if err != nil { + f.InternalError(err, "Error during marshalling response", w, r) + return + } + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(201) + fmt.Fprint(w, string(bytes)) +} + +// Router +func (f *NeutronAPIFixture) routerHandler(w http.ResponseWriter, r *http.Request) { + f.LogRequest(r) + switch r.Method { + case "GET": + f.getRouter(w, r) + case "POST": + f.postRouter(w, r) + case "PUT": + f.putRouter(w, r) + default: + f.UnexpectedRequest(w, r) + return + } +} + +func (f *NeutronAPIFixture) getRouter(w http.ResponseWriter, r *http.Request) { + items := strings.Split(r.URL.Path, "/") + if len(items) == 4 { + var n struct { + Routers []routers.Router `json:"routers"` + } + n.Routers = []routers.Router{} + query := r.URL.Query() + name := query["name"] + tenantID := query["tenant_id"] + for _, router := range f.Routers { + if len(name) > 0 && name[0] != router.Name { + continue + } + if len(tenantID) > 0 && tenantID[0] != router.TenantID { + continue + } + n.Routers = append(n.Routers, router) + } + bytes, err := json.Marshal(&n) + if err != nil { + f.InternalError(err, "Error during marshalling response", w, r) + return + } + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(200) + fmt.Fprint(w, string(bytes)) + } +} + +func (f *NeutronAPIFixture) postRouter(w http.ResponseWriter, r *http.Request) { + bytes, err := io.ReadAll(r.Body) + if err != nil { + f.InternalError(err, "Error reading request body", w, r) + return + } + + var n struct { + Router routers.Router `json:"router"` + } + + err = json.Unmarshal(bytes, &n) + if err != nil { + f.InternalError(err, "Error during marshalling response", w, r) + return + } + + routerID := uuid.New().String() + n.Router.ID = routerID + f.Routers[routerID] = n.Router + + bytes, err = json.Marshal(&n) + if err != nil { + f.InternalError(err, "Error during marshalling response", w, r) + return + } + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(201) + fmt.Fprint(w, string(bytes)) +} + +func (f *NeutronAPIFixture) putRouter(w http.ResponseWriter, r *http.Request) { + items := strings.Split(r.URL.Path, "/") + routerID := items[4] + action := items[5] + + if action == "add_router_interface" { + bytes, err := io.ReadAll(r.Body) + if err != nil { + f.InternalError(err, "Error reading request body", w, r) + return + } + + var n struct { + SubnetID string `json:"subnet_id"` + PortID string `json:"port_id"` + } + + err = json.Unmarshal(bytes, &n) + if err != nil { + f.InternalError(err, "Error during marshalling response", w, r) + return + } + + var subnetID string + if n.SubnetID == "" { + subnetID = f.Ports[n.PortID].FixedIPs[0].SubnetID + } else { + subnetID = n.SubnetID + } + f.InterfaceInfos[fmt.Sprintf("%s:%s", routerID, subnetID)] = routers.InterfaceInfo{ + SubnetID: subnetID, + PortID: n.PortID, + } + + bytes, err = json.Marshal(&n) + if err != nil { + f.InternalError(err, "Error during marshalling response", w, r) + return + } + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(200) + fmt.Fprint(w, string(bytes)) + } +} + +// Quota func (f *NeutronAPIFixture) quotasHandler(w http.ResponseWriter, r *http.Request) { f.LogRequest(r) switch r.Method { @@ -125,7 +645,13 @@ func AddNeutronAPIFixture(log logr.Logger, server *api.FakeAPIServer) *NeutronAP SecurityGroup: 10, SecurityGroupRule: 10, }, - Quotas: map[string]quotas.Quota{}, + Quotas: map[string]quotas.Quota{}, + Networks: map[string]networks.Network{}, + Subnets: map[string]subnets.Subnet{}, + SecGroups: map[string]groups.SecGroup{}, + Ports: map[string]ports.Port{}, + Routers: map[string]routers.Router{}, + InterfaceInfos: map[string]routers.InterfaceInfo{}, } return fixture } diff --git a/tests/functional/nova_api_fixture.go b/tests/functional/nova_api_fixture.go index e31a88f1..7b0d6b97 100644 --- a/tests/functional/nova_api_fixture.go +++ b/tests/functional/nova_api_fixture.go @@ -25,6 +25,7 @@ import ( "github.com/go-logr/logr" + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/quotasets" api "github.com/openstack-k8s-operators/lib-common/modules/test/apis" @@ -34,6 +35,7 @@ type NovaAPIFixture struct { api.APIFixture QuotaSets map[string]quotasets.QuotaSet DefaultQuotaSet quotasets.QuotaSet + KeyPairs map[string]keypairs.KeyPair } func (f *NovaAPIFixture) registerHandler(handler api.Handler) { @@ -41,9 +43,96 @@ func (f *NovaAPIFixture) registerHandler(handler api.Handler) { } func (f *NovaAPIFixture) Setup() { + f.registerHandler(api.Handler{Pattern: "/os-keypairs", Func: f.keyPairHandler}) + f.registerHandler(api.Handler{Pattern: "/os-keypairs/", Func: f.keyPairHandler}) f.registerHandler(api.Handler{Pattern: "/os-quota-sets/", Func: f.quotaSetsHandler}) } +func (f *NovaAPIFixture) keyPairHandler(w http.ResponseWriter, r *http.Request) { + f.LogRequest(r) + switch r.Method { + case "GET": + f.getKeyPair(w, r) + case "POST": + f.postKeyPair(w, r) + case "DELETE": + f.deleteKeyPair(w, r) + default: + f.UnexpectedRequest(w, r) + return + } +} + +func (f *NovaAPIFixture) getKeyPair(w http.ResponseWriter, r *http.Request) { + items := strings.Split(r.URL.Path, "/") + if len(items) == 3 { + type pair struct { + KeyPair keypairs.KeyPair `json:"keypair"` + } + var k struct { + KeyPairs []pair `json:"keypairs"` + } + k.KeyPairs = []pair{} + query := r.URL.Query() + userID := query["user_id"] + for _, keypair := range f.KeyPairs { + if len(userID) > 0 && userID[0] != keypair.UserID { + continue + } + k.KeyPairs = append(k.KeyPairs, pair{KeyPair: keypair}) + } + bytes, err := json.Marshal(&k) + if err != nil { + f.InternalError(err, "Error during marshalling response", w, r) + return + } + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(200) + fmt.Fprint(w, string(bytes)) + } +} + +func (f *NovaAPIFixture) postKeyPair(w http.ResponseWriter, r *http.Request) { + bytes, err := io.ReadAll(r.Body) + if err != nil { + f.InternalError(err, "Error reading request body", w, r) + return + } + + var k struct { + KeyPair keypairs.KeyPair `json:"keypair"` + } + + err = json.Unmarshal(bytes, &k) + if err != nil { + f.InternalError(err, "Error during marshalling response", w, r) + return + } + + f.KeyPairs[k.KeyPair.Name] = k.KeyPair + + bytes, err = json.Marshal(&k) + if err != nil { + f.InternalError(err, "Error during marshalling response", w, r) + return + } + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(201) + fmt.Fprint(w, string(bytes)) +} + +func (f *NovaAPIFixture) deleteKeyPair(w http.ResponseWriter, r *http.Request) { + items := strings.Split(r.URL.Path, "/") + keypair := items[len(items)-1] + + delete(f.KeyPairs, keypair) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(202) +} + func (f *NovaAPIFixture) quotaSetsHandler(w http.ResponseWriter, r *http.Request) { f.LogRequest(r) switch r.Method { @@ -128,6 +217,7 @@ func AddNovaAPIFixture(log logr.Logger, server *api.FakeAPIServer) *NovaAPIFixtu ServerGroupMembers: 10, }, QuotaSets: map[string]quotasets.QuotaSet{}, + KeyPairs: map[string]keypairs.KeyPair{}, } return fixture } diff --git a/tests/functional/octavia_controller_test.go b/tests/functional/octavia_controller_test.go index af7630ec..e586d920 100644 --- a/tests/functional/octavia_controller_test.go +++ b/tests/functional/octavia_controller_test.go @@ -20,13 +20,16 @@ import ( "fmt" "github.com/google/uuid" + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers" + "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" + "github.com/gophercloud/gophercloud/openstack/networking/v2/subnets" . "github.com/onsi/ginkgo/v2" //revive:disable:dot-imports . "github.com/onsi/gomega" //revive:disable:dot-imports "k8s.io/apimachinery/pkg/types" corev1 "k8s.io/api/core/v1" - keystone_helpers "github.com/openstack-k8s-operators/keystone-operator/api/test/helpers" "github.com/openstack-k8s-operators/lib-common/modules/common/condition" //revive:disable-next-line:dot-imports @@ -36,6 +39,70 @@ import ( "github.com/openstack-k8s-operators/octavia-operator/pkg/octavia" ) +func createAndSimulateKeystone( + octaviaName types.NamespacedName, +) APIFixtures { + apiFixtures := SetupAPIFixtures(logger) + keystoneName := keystone.CreateKeystoneAPIWithFixture(namespace, apiFixtures.Keystone) + DeferCleanup(keystone.DeleteKeystoneAPI, keystoneName) + keystonePublicEndpoint := fmt.Sprintf("http://keystone-for-%s-public", octaviaName.Name) + SimulateKeystoneReady(keystoneName, keystonePublicEndpoint, apiFixtures.Keystone.Endpoint()) + return apiFixtures +} + +func createAndSimulateOctaviaSecrets( + octaviaName types.NamespacedName, +) { + DeferCleanup(k8sClient.Delete, ctx, CreateOctaviaSecret(octaviaName.Namespace)) + DeferCleanup(k8sClient.Delete, ctx, CreateOctaviaCaPassphraseSecret(octaviaName.Namespace, octaviaName.Name)) + SimulateOctaviaCertsSecret(octaviaName.Namespace, octaviaName.Name) +} + +func createAndSimulateTransportURL( + transportURLName types.NamespacedName, + transportURLSecretName types.NamespacedName, +) { + DeferCleanup(k8sClient.Delete, ctx, CreateTransportURL(transportURLName)) + DeferCleanup(k8sClient.Delete, ctx, CreateTransportURLSecret(transportURLSecretName)) + infra.SimulateTransportURLReady(transportURLName) +} + +func createAndSimulateDB(spec map[string]interface{}) { + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + namespace, + spec["databaseInstance"].(string), + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + mariadb.CreateMariaDBAccount(namespace, spec["databaseAccount"].(string), mariadbv1.MariaDBAccountSpec{ + Secret: "osp-secret", + UserName: "octavia", + }) + mariadb.CreateMariaDBAccount(namespace, spec["persistenceDatabaseAccount"].(string), mariadbv1.MariaDBAccountSpec{ + Secret: "osp-secret", + UserName: "octavia", + }) + mariadb.CreateMariaDBDatabase(namespace, octavia.DatabaseCRName, mariadbv1.MariaDBDatabaseSpec{}) + mariadb.CreateMariaDBDatabase(namespace, octavia.PersistenceDatabaseCRName, mariadbv1.MariaDBDatabaseSpec{}) + mariadb.SimulateMariaDBAccountCompleted(types.NamespacedName{Namespace: namespace, Name: spec["databaseAccount"].(string)}) + mariadb.SimulateMariaDBDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: octavia.DatabaseCRName}) + mariadb.SimulateMariaDBAccountCompleted(types.NamespacedName{Namespace: namespace, Name: spec["persistenceDatabaseAccount"].(string)}) + mariadb.SimulateMariaDBDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: octavia.PersistenceDatabaseCRName}) +} + +func createAndSimulateOctaviaAPI(octaviaName types.NamespacedName) { + octaviaAPIName := types.NamespacedName{ + Namespace: namespace, + Name: fmt.Sprintf("%s-api", octaviaName.Name), + } + DeferCleanup(th.DeleteInstance, CreateOctaviaAPI(octaviaAPIName, GetDefaultOctaviaAPISpec())) + SimulateOctaviaAPIReady(octaviaAPIName) +} + var _ = Describe("Octavia controller", func() { var name string var spec map[string]interface{} @@ -134,9 +201,9 @@ var _ = Describe("Octavia controller", func() { When("a proper secret is provider, TransportURL is created", func() { BeforeEach(func() { DeferCleanup(th.DeleteInstance, CreateOctavia(octaviaName, spec)) - DeferCleanup(k8sClient.Delete, ctx, CreateOctaviaSecret(namespace)) - DeferCleanup(k8sClient.Delete, ctx, CreateTransportURLSecret(transportURLSecretName)) - infra.SimulateTransportURLReady(transportURLName) + + createAndSimulateOctaviaSecrets(octaviaName) + createAndSimulateTransportURL(transportURLName, transportURLSecretName) }) It("should be in state of having the input ready", func() { @@ -168,21 +235,11 @@ var _ = Describe("Octavia controller", func() { // Certs When("Certificates are created", func() { - var keystoneAPIFixture *keystone_helpers.KeystoneAPIFixture - BeforeEach(func() { - keystoneAPIFixture, _, _ = SetupAPIFixtures(logger) - keystoneName := keystone.CreateKeystoneAPIWithFixture(namespace, keystoneAPIFixture) - DeferCleanup(keystone.DeleteKeystoneAPI, keystoneName) - keystonePublicEndpoint := fmt.Sprintf("http://keystone-for-%s-public", octaviaName.Name) - SimulateKeystoneReady(keystoneName, keystonePublicEndpoint, keystoneAPIFixture.Endpoint()) - - DeferCleanup(k8sClient.Delete, ctx, CreateOctaviaSecret(namespace)) - DeferCleanup(k8sClient.Delete, ctx, CreateOctaviaCaPassphraseSecret(namespace, octaviaName.Name)) + createAndSimulateKeystone(octaviaName) - DeferCleanup(k8sClient.Delete, ctx, CreateTransportURL(transportURLName)) - DeferCleanup(k8sClient.Delete, ctx, CreateTransportURLSecret(transportURLSecretName)) - infra.SimulateTransportURLReady(transportURLName) + createAndSimulateOctaviaSecrets(octaviaName) + createAndSimulateTransportURL(transportURLName, transportURLSecretName) DeferCleanup(th.DeleteInstance, CreateOctavia(octaviaName, spec)) }) @@ -215,24 +272,13 @@ var _ = Describe("Octavia controller", func() { // Quotas When("Quotas are created", func() { - var keystoneAPIFixture *keystone_helpers.KeystoneAPIFixture - var novaAPIFixture *NovaAPIFixture - var neutronAPIFixture *NeutronAPIFixture + var apiFixtures APIFixtures BeforeEach(func() { - keystoneAPIFixture, novaAPIFixture, neutronAPIFixture = SetupAPIFixtures(logger) - keystoneName := keystone.CreateKeystoneAPIWithFixture(namespace, keystoneAPIFixture) - DeferCleanup(keystone.DeleteKeystoneAPI, keystoneName) - keystonePublicEndpoint := fmt.Sprintf("http://keystone-for-%s-public", octaviaName.Name) - SimulateKeystoneReady(keystoneName, keystonePublicEndpoint, keystoneAPIFixture.Endpoint()) + apiFixtures = createAndSimulateKeystone(octaviaName) - DeferCleanup(k8sClient.Delete, ctx, CreateOctaviaSecret(namespace)) - DeferCleanup(k8sClient.Delete, ctx, CreateOctaviaCaPassphraseSecret(namespace, octaviaName.Name)) - SimulateOctaviaCertsSecret(namespace, octaviaName.Name) - - DeferCleanup(k8sClient.Delete, ctx, CreateTransportURL(transportURLName)) - DeferCleanup(k8sClient.Delete, ctx, CreateTransportURLSecret(transportURLSecretName)) - infra.SimulateTransportURLReady(transportURLName) + createAndSimulateOctaviaSecrets(octaviaName) + createAndSimulateTransportURL(transportURLName, transportURLSecretName) DeferCleanup(th.DeleteInstance, CreateOctavia(octaviaName, spec)) }) @@ -248,14 +294,14 @@ var _ = Describe("Octavia controller", func() { instance := GetOctavia(octaviaName) project := GetProject(instance.Spec.TenantName) - quotaset := novaAPIFixture.QuotaSets[project.ID] + quotaset := apiFixtures.Nova.QuotaSets[project.ID] Expect(quotaset.RAM).To(Equal(-1)) Expect(quotaset.Cores).To(Equal(-1)) Expect(quotaset.Instances).To(Equal(-1)) Expect(quotaset.ServerGroups).To(Equal(-1)) Expect(quotaset.ServerGroupMembers).To(Equal(-1)) - quota := neutronAPIFixture.Quotas[project.ID] + quota := apiFixtures.Neutron.Quotas[project.ID] Expect(quota.Port).To(Equal(-1)) Expect(quota.SecurityGroup).To(Equal(-1)) Expect(quota.SecurityGroupRule).To(Equal(-1)) @@ -266,22 +312,11 @@ var _ = Describe("Octavia controller", func() { // DB When("DB is created", func() { - var keystoneAPIFixture *keystone_helpers.KeystoneAPIFixture - BeforeEach(func() { - keystoneAPIFixture, _, _ = SetupAPIFixtures(logger) - keystoneName := keystone.CreateKeystoneAPIWithFixture(namespace, keystoneAPIFixture) - DeferCleanup(keystone.DeleteKeystoneAPI, keystoneName) - keystonePublicEndpoint := fmt.Sprintf("http://keystone-for-%s-public", octaviaName.Name) - SimulateKeystoneReady(keystoneName, keystonePublicEndpoint, keystoneAPIFixture.Endpoint()) + createAndSimulateKeystone(octaviaName) - DeferCleanup(k8sClient.Delete, ctx, CreateOctaviaSecret(namespace)) - DeferCleanup(k8sClient.Delete, ctx, CreateOctaviaCaPassphraseSecret(namespace, octaviaName.Name)) - SimulateOctaviaCertsSecret(namespace, octaviaName.Name) - - DeferCleanup(k8sClient.Delete, ctx, CreateTransportURL(transportURLName)) - DeferCleanup(k8sClient.Delete, ctx, CreateTransportURLSecret(transportURLSecretName)) - infra.SimulateTransportURLReady(transportURLName) + createAndSimulateOctaviaSecrets(octaviaName) + createAndSimulateTransportURL(transportURLName, transportURLSecretName) DeferCleanup(th.DeleteInstance, CreateOctavia(octaviaName, spec)) @@ -323,40 +358,17 @@ var _ = Describe("Octavia controller", func() { // Config When("The Config Secrets are created", func() { - var keystoneAPIFixture *keystone_helpers.KeystoneAPIFixture BeforeEach(func() { - keystoneAPIFixture, _, _ = SetupAPIFixtures(logger) - keystoneName := keystone.CreateKeystoneAPIWithFixture(namespace, keystoneAPIFixture) - DeferCleanup(keystone.DeleteKeystoneAPI, keystoneName) - keystonePublicEndpoint := fmt.Sprintf("http://keystone-for-%s-public", octaviaName.Name) - SimulateKeystoneReady(keystoneName, keystonePublicEndpoint, keystoneAPIFixture.Endpoint()) + createAndSimulateKeystone(octaviaName) - DeferCleanup(k8sClient.Delete, ctx, CreateOctaviaSecret(namespace)) - DeferCleanup(k8sClient.Delete, ctx, CreateOctaviaCaPassphraseSecret(namespace, octaviaName.Name)) - SimulateOctaviaCertsSecret(namespace, octaviaName.Name) + createAndSimulateOctaviaSecrets(octaviaName) + createAndSimulateTransportURL(transportURLName, transportURLSecretName) - DeferCleanup(k8sClient.Delete, ctx, CreateTransportURL(transportURLName)) - DeferCleanup(k8sClient.Delete, ctx, CreateTransportURLSecret(transportURLSecretName)) - infra.SimulateTransportURLReady(transportURLName) + createAndSimulateDB(spec) DeferCleanup(th.DeleteInstance, CreateOctavia(octaviaName, spec)) - DeferCleanup( - mariadb.DeleteDBService, - mariadb.CreateDBService( - namespace, - GetOctavia(octaviaName).Spec.DatabaseInstance, - corev1.ServiceSpec{ - Ports: []corev1.ServicePort{{Port: 3306}}, - }, - ), - ) - - mariadb.SimulateMariaDBAccountCompleted(types.NamespacedName{Namespace: namespace, Name: GetOctavia(octaviaName).Spec.DatabaseAccount}) - mariadb.SimulateMariaDBDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: octavia.DatabaseCRName}) - mariadb.SimulateMariaDBAccountCompleted(types.NamespacedName{Namespace: namespace, Name: GetOctavia(octaviaName).Spec.PersistenceDatabaseAccount}) - mariadb.SimulateMariaDBDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: octavia.PersistenceDatabaseCRName}) th.SimulateJobSuccess(types.NamespacedName{Namespace: namespace, Name: octaviaName.Name + "-db-sync"}) }) @@ -431,11 +443,195 @@ var _ = Describe("Octavia controller", func() { }) }) - // Create Networks Annotation + // Networks Annotation + When("Network Annotation is created", func() { + BeforeEach(func() { + createAndSimulateKeystone(octaviaName) + + createAndSimulateOctaviaSecrets(octaviaName) + createAndSimulateTransportURL(transportURLName, transportURLSecretName) + + createAndSimulateDB(spec) + + DeferCleanup(k8sClient.Delete, ctx, CreateNAD(types.NamespacedName{ + Name: spec["octaviaNetworkAttachment"].(string), + Namespace: namespace, + })) + + DeferCleanup(th.DeleteInstance, CreateOctavia(octaviaName, spec)) + }) + + It("should set the NetworkAttachementReady condition", func() { + th.ExpectCondition( + octaviaName, + ConditionGetterFunc(OctaviaConditionGetter), + condition.NetworkAttachmentsReadyCondition, + corev1.ConditionTrue, + ) + }) + }) // API Deployment // Network Management + When("The management network is created", func() { + var apiFixtures APIFixtures + + BeforeEach(func() { + apiFixtures = createAndSimulateKeystone(octaviaName) + + createAndSimulateOctaviaSecrets(octaviaName) + createAndSimulateTransportURL(transportURLName, transportURLSecretName) + + createAndSimulateDB(spec) + + createAndSimulateOctaviaAPI(octaviaName) + + DeferCleanup(k8sClient.Delete, ctx, CreateNAD(types.NamespacedName{ + Name: spec["octaviaNetworkAttachment"].(string), + Namespace: namespace, + })) + + DeferCleanup(k8sClient.Delete, ctx, CreateNode(types.NamespacedName{ + Namespace: namespace, + Name: "node1", + })) + + DeferCleanup(th.DeleteInstance, CreateOctavia(octaviaName, spec)) + + th.SimulateJobSuccess(types.NamespacedName{Namespace: namespace, Name: octaviaName.Name + "-db-sync"}) + }) + + It("should create appropriate resources in Neutron", func() { + // Replace with condition for LbMgmtNetwork when it's merged + th.ExpectCondition( + octaviaName, + ConditionGetterFunc(OctaviaConditionGetter), + condition.ExposeServiceReadyCondition, + corev1.ConditionTrue, + ) + + instance := GetOctavia(octaviaName) + tenant := GetProject(instance.Spec.TenantName) + adminTenant := GetProject(octavia.AdminTenant) + + nadConfig := GetNADConfig(types.NamespacedName{ + Name: instance.Spec.OctaviaNetworkAttachment, + Namespace: namespace}) + + // Networks + expectedNetworks := map[string]networks.Network{ + octavia.LbMgmtNetName: { + Description: octavia.LbMgmtNetDescription, + TenantID: tenant.ID, + AvailabilityZoneHints: instance.Spec.LbMgmtNetworks.AvailabilityZones, + }, + octavia.LbProvNetName: { + Description: octavia.LbProvNetDescription, + TenantID: adminTenant.ID, + AvailabilityZoneHints: instance.Spec.LbMgmtNetworks.AvailabilityZones, + }, + } + + resultNetworks := map[string]networks.Network{} + for _, network := range apiFixtures.Neutron.Networks { + resultNetworks[network.Name] = network + } + Expect(resultNetworks).To(HaveLen(2)) + for name, expectedNetwork := range expectedNetworks { + network := resultNetworks[name] + Expect(network).ToNot(Equal(networks.Network{}), "Network %s doesn't appear to exist", name) + Expect(network.Description).To(Equal(expectedNetwork.Description)) + Expect(network.TenantID).To(Equal(expectedNetwork.TenantID)) + Expect(network.AvailabilityZoneHints).To(Equal(expectedNetwork.AvailabilityZoneHints)) + } + + lbMgmtPortAddress := "" + lbMgmtPortID := "" + for _, port := range apiFixtures.Neutron.Ports { + if port.Name == octavia.LbMgmtRouterPortName { + lbMgmtPortAddress = port.FixedIPs[0].IPAddress + lbMgmtPortID = port.ID + break + } + } + // Subnets + expectedSubnets := map[string]subnets.Subnet{ + octavia.LbMgmtSubnetName: { + Description: octavia.LbMgmtSubnetDescription, + TenantID: tenant.ID, + NetworkID: resultNetworks[octavia.LbMgmtNetName].ID, + CIDR: nadConfig.IPAM.Routes[0].Destination.String(), + HostRoutes: []subnets.HostRoute{{ + DestinationCIDR: nadConfig.IPAM.CIDR.String(), + NextHop: lbMgmtPortAddress, + }}, + }, + octavia.LbProvSubnetName: { + Description: octavia.LbProvSubnetDescription, + TenantID: adminTenant.ID, + NetworkID: resultNetworks[octavia.LbProvNetName].ID, + CIDR: nadConfig.IPAM.CIDR.String(), + }, + } + + resultSubnets := map[string]subnets.Subnet{} + for _, subnet := range apiFixtures.Neutron.Subnets { + resultSubnets[subnet.Name] = subnet + } + Expect(resultSubnets).To(HaveLen(2)) + for name, expectedSubnet := range expectedSubnets { + subnet := resultSubnets[name] + Expect(subnet).ToNot(Equal(subnets.Subnet{}), "Subnet %s doesn't appear to exist", name) + Expect(subnet.Description).To(Equal(expectedSubnet.Description)) + Expect(subnet.TenantID).To(Equal(expectedSubnet.TenantID)) + Expect(subnet.NetworkID).To(Equal(expectedSubnet.NetworkID)) + Expect(subnet.CIDR).To(Equal(expectedSubnet.CIDR)) + Expect(subnet.HostRoutes).To(Equal(expectedSubnet.HostRoutes)) + } + + // Routers + expectedRouters := map[string]routers.Router{ + octavia.LbRouterName: { + GatewayInfo: routers.GatewayInfo{ + NetworkID: resultNetworks[octavia.LbProvNetName].ID, + ExternalFixedIPs: []routers.ExternalFixedIP{ + { + SubnetID: resultSubnets[octavia.LbProvSubnetName].ID, + }, + }, + }, + AvailabilityZoneHints: instance.Spec.LbMgmtNetworks.AvailabilityZones, + }, + } + + resultRouters := map[string]routers.Router{} + for _, router := range apiFixtures.Neutron.Routers { + resultRouters[router.Name] = router + } + Expect(resultRouters).To(HaveLen(1)) + for name, expectedRouter := range expectedRouters { + router := resultRouters[name] + Expect(router).ToNot(Equal(routers.Router{}), "Router %s doesn't appear to exist", name) + Expect(router.GatewayInfo.NetworkID).To(Equal(expectedRouter.GatewayInfo.NetworkID)) + Expect(router.GatewayInfo.ExternalFixedIPs[0].SubnetID).To(Equal(expectedRouter.GatewayInfo.ExternalFixedIPs[0].SubnetID)) + Expect(router.AvailabilityZoneHints).To(Equal(expectedRouter.AvailabilityZoneHints)) + } + + expectedInterfaces := map[string]routers.InterfaceInfo{ + fmt.Sprintf("%s:%s", resultRouters[octavia.LbRouterName].ID, resultSubnets[octavia.LbMgmtSubnetName].ID): { + SubnetID: resultSubnets[octavia.LbMgmtSubnetName].ID, + PortID: lbMgmtPortID, + }, + } + for id, expectedInterfaces := range expectedInterfaces { + iface := apiFixtures.Neutron.InterfaceInfos[id] + Expect(iface).ToNot(Equal(routers.InterfaceInfo{}), "Interface %s doesn't appear to exist", id) + Expect(iface.SubnetID).To(Equal(expectedInterfaces.SubnetID)) + Expect(iface.PortID).To(Equal(expectedInterfaces.PortID)) + } + }) + }) // Predictable IPs @@ -444,6 +640,101 @@ var _ = Describe("Octavia controller", func() { // Rsyslog Daemonset // Amp SSH Config + When("The Amphora SSH config map is created", func() { + var apiFixtures APIFixtures + + BeforeEach(func() { + apiFixtures = createAndSimulateKeystone(octaviaName) + + createAndSimulateOctaviaSecrets(octaviaName) + createAndSimulateTransportURL(transportURLName, transportURLSecretName) + + createAndSimulateDB(spec) + + createAndSimulateOctaviaAPI(octaviaName) + + DeferCleanup(k8sClient.Delete, ctx, CreateNAD(types.NamespacedName{ + Name: spec["octaviaNetworkAttachment"].(string), + Namespace: namespace, + })) + + DeferCleanup(k8sClient.Delete, ctx, CreateNode(types.NamespacedName{ + Namespace: namespace, + Name: "node1", + })) + + DeferCleanup(th.DeleteInstance, CreateOctavia(octaviaName, spec)) + + th.SimulateJobSuccess(types.NamespacedName{Namespace: namespace, Name: octaviaName.Name + "-db-sync"}) + }) + + It("should set OctaviaAmphoraSSHReady condition", func() { + th.ExpectCondition( + octaviaName, + ConditionGetterFunc(OctaviaConditionGetter), + octaviav1.OctaviaAmphoraSSHReadyCondition, + corev1.ConditionTrue, + ) + }) + + It("should upload a new keypair", func() { + keyPairs := apiFixtures.Nova.KeyPairs + Expect(keyPairs[octavia.NovaKeyPairName]).ShouldNot(Equal(keypairs.KeyPair{})) + }) + + It("should set a key in the config map", func() { + instance := GetOctavia(octaviaName) + configMap := th.GetConfigMap(types.NamespacedName{ + Name: instance.Spec.LoadBalancerSSHPubKey, + Namespace: namespace}) + Expect(configMap).ShouldNot(BeNil()) + Expect(configMap.Data["key"]).Should( + ContainSubstring("ecdsa-")) + }) + }) + + When("The Amphora SSH config map and the keypair already exist", func() { + var apiFixtures APIFixtures + + BeforeEach(func() { + apiFixtures = createAndSimulateKeystone(octaviaName) + apiFixtures.Nova.KeyPairs = map[string]keypairs.KeyPair{ + "octavia-ssh-keypair": { + Name: "octavia-ssh-keypair", + PublicKey: "foobar", + UserID: apiFixtures.Keystone.Users["octavia"].ID, + }} + + createAndSimulateOctaviaSecrets(octaviaName) + createAndSimulateTransportURL(transportURLName, transportURLSecretName) + + createAndSimulateDB(spec) + + createAndSimulateOctaviaAPI(octaviaName) + + DeferCleanup(k8sClient.Delete, ctx, CreateNAD(types.NamespacedName{ + Name: spec["octaviaNetworkAttachment"].(string), + Namespace: namespace, + })) + + DeferCleanup(k8sClient.Delete, ctx, CreateNode(types.NamespacedName{ + Namespace: namespace, + Name: "node1", + })) + + DeferCleanup(th.DeleteInstance, CreateSSHPubKey()) + + DeferCleanup(th.DeleteInstance, CreateOctavia(octaviaName, spec)) + + th.SimulateJobSuccess(types.NamespacedName{Namespace: namespace, Name: octaviaName.Name + "-db-sync"}) + }) + + // PENDING https://issues.redhat.com/browse/OSPRH-10543 + PIt("should not update the keypair", func() { + keyPairs := apiFixtures.Nova.KeyPairs + Expect(keyPairs["octavia-ssh-keypair"].PublicKey).Should(Equal("foobar")) + }) + }) // Amphora Image })