Skip to content

Commit

Permalink
Merge pull request #9722 from rifelpet/openstack-cloudmock-part4
Browse files Browse the repository at this point in the history
Implement Openstack cloudmock, add integration test
  • Loading branch information
k8s-ci-robot authored Aug 11, 2020
2 parents cdbb780 + 6991655 commit 40d9dbc
Show file tree
Hide file tree
Showing 33 changed files with 2,670 additions and 39 deletions.
1 change: 1 addition & 0 deletions cloudmock/openstack/mockblockstorage/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//cloudmock/openstack:go_default_library",
"//vendor/github.com/google/uuid:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones:go_default_library",
],
Expand Down
42 changes: 41 additions & 1 deletion cloudmock/openstack/mockblockstorage/availabilityzones.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,56 @@ limitations under the License.
package mockblockstorage

import (
"encoding/json"
"fmt"
"net/http"

"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones"
)

type availabilityZoneListResponse struct {
AvailabilityZones []availabilityzones.AvailabilityZone `json:"availabilityZoneInfo"`
}

func (m *MockClient) mockAvailabilityZones() {
// There is no "create" API for zones so they are directly added here
m.availabilityZones["us-test1-a"] = availabilityzones.AvailabilityZone{
ZoneName: "us-east1-a",
}

handler := func(w http.ResponseWriter, r *http.Request) {
m.mutex.Lock()
defer m.mutex.Unlock()

w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)

switch r.Method {
case http.MethodGet:
m.listAvailabilityZones(w)
default:
w.WriteHeader(http.StatusBadRequest)
}
}
m.Mux.HandleFunc("/os-availability-zone", handler)
}

func (m *MockClient) listAvailabilityZones(w http.ResponseWriter) {
w.WriteHeader(http.StatusOK)

availabilityzones := make([]availabilityzones.AvailabilityZone, 0)
for _, k := range m.availabilityZones {
availabilityzones = append(availabilityzones, k)
}

resp := availabilityZoneListResponse{
AvailabilityZones: availabilityzones,
}
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")
}
}
166 changes: 165 additions & 1 deletion cloudmock/openstack/mockblockstorage/volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,182 @@ limitations under the License.
package mockblockstorage

import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"regexp"
"strings"

"github.com/google/uuid"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes"
)

type volumeListResponse struct {
Volumes []volumes.Volume `json:"volumes"`
}

type volumeGetResponse struct {
Volume volumes.Volume `json:"volume"`
}

type volumeCreateRequest struct {
Volume volumes.CreateOpts `json:"volume"`
}

type volumeUpdateRequest struct {
Volume volumes.UpdateOpts `json:"volume"`
}

func (m *MockClient) mockVolumes() {
re := regexp.MustCompile(`/volumes/?`)

handler := func(w http.ResponseWriter, r *http.Request) {
m.mutex.Lock()
defer m.mutex.Unlock()

w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
volID := re.ReplaceAllString(r.URL.Path, "")
switch r.Method {
case http.MethodGet:
if volID == "detail" {
r.ParseForm()
m.listVolumes(w, r.Form)
} else {
m.getVolume(w, volID)
}
case http.MethodPost:
m.createVolume(w, r)
case http.MethodPut:
m.updateVolume(w, r, volID)
case http.MethodDelete:
m.deleteVolume(w, volID)
default:
w.WriteHeader(http.StatusBadRequest)
}
}
m.Mux.HandleFunc("/volumes/", handler)
m.Mux.HandleFunc("/volumes", handler)
}

func (m *MockClient) listVolumes(w http.ResponseWriter, vals url.Values) {

w.WriteHeader(http.StatusOK)

vols := filterVolumes(m.volumes, vals)

resp := volumeListResponse{
Volumes: vols,
}
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")
}
}

func (m *MockClient) getVolume(w http.ResponseWriter, volumeID string) {
if vol, ok := m.volumes[volumeID]; ok {
resp := volumeGetResponse{
Volume: vol,
}
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")
}
} else {
w.WriteHeader(http.StatusNotFound)
}
}

func (m *MockClient) updateVolume(w http.ResponseWriter, r *http.Request, volumeID string) {
if _, ok := m.volumes[volumeID]; !ok {
w.WriteHeader(http.StatusNotFound)
return
}
var update volumeUpdateRequest
err := json.NewDecoder(r.Body).Decode(&update)
if err != nil {
panic("error decoding update volume request")
}
vol := m.volumes[volumeID]
vol.Metadata = update.Volume.Metadata
w.WriteHeader(http.StatusOK)
}

func (m *MockClient) deleteVolume(w http.ResponseWriter, volumeID string) {
if _, ok := m.volumes[volumeID]; ok {
delete(m.volumes, volumeID)
w.WriteHeader(http.StatusOK)
} else {
w.WriteHeader(http.StatusNotFound)
}
}

func (m *MockClient) createVolume(w http.ResponseWriter, r *http.Request) {
var create volumeCreateRequest
err := json.NewDecoder(r.Body).Decode(&create)
if err != nil {
panic("error decoding create volume request")
}

w.WriteHeader(http.StatusAccepted)

v := volumes.Volume{
ID: uuid.New().String(),
Name: create.Volume.Name,
Size: create.Volume.Size,
AvailabilityZone: create.Volume.AvailabilityZone,
Metadata: create.Volume.Metadata,
VolumeType: create.Volume.VolumeType,
}
m.volumes[v.ID] = v

resp := volumeGetResponse{
Volume: v,
}
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")
}
}

func filterVolumes(allVolumes map[string]volumes.Volume, vals url.Values) []volumes.Volume {
vols := make([]volumes.Volume, 0)
for _, volume := range allVolumes {
name := vals.Get("name")
metadata := vals.Get("metadata")
// metadata is decoded as: {'k8s.io/etcd/main':'1/1',+'k8s.io/role/master':'1'}
// Replacing single quotes with double quotes makes it valid JSON
metadata = strings.ReplaceAll(metadata, "'", "\"")
parsedMetadata := make(map[string]string)
json.Unmarshal([]byte(metadata), &parsedMetadata)

if name != "" && volume.Name != name {
continue
}
match := true
for k, v := range parsedMetadata {
if volume.Metadata[k] != v {
match = false
break
}
}
if !match {
continue
}
vols = append(vols, volume)
}
return vols
}
3 changes: 3 additions & 0 deletions cloudmock/openstack/mockcompute/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//cloudmock/openstack:go_default_library",
"//pkg/pki:go_default_library",
"//upup/pkg/fi:go_default_library",
"//vendor/github.com/google/uuid:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors:go_default_library",
Expand Down
3 changes: 0 additions & 3 deletions cloudmock/openstack/mockcompute/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,5 @@ func (m *MockClient) All() map[string]interface{} {
for id, kp := range m.keyPairs {
all[id] = kp
}
for id, s := range m.servers {
all[id] = s
}
return all
}
Loading

0 comments on commit 40d9dbc

Please sign in to comment.