Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions internal/api/network/floating_ip.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package network

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

"github.com/JSYoo5B/SandStack/internal/api/respond"
appnetwork "github.com/JSYoo5B/SandStack/internal/app/network"
"github.com/go-chi/chi/v5"
)

func (h Handler) listFloatingIPs(w http.ResponseWriter, _ *http.Request) {
respond.JSON(w, http.StatusOK, floatingIPListResponse{
FloatingIPs: toFloatingIPDocuments(h.service.ListFloatingIPs()),
})
}

func (h Handler) createFloatingIP(w http.ResponseWriter, r *http.Request) {
var request createFloatingIPRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
respond.Error(w, http.StatusBadRequest, "invalid JSON request body")
return
}

floatingIP := h.service.CreateFloatingIP(request.createFloatingIP())
respond.JSON(w, http.StatusCreated, floatingIPResponse{
FloatingIP: toFloatingIPDocument(floatingIP),
})
}

func (h Handler) getFloatingIP(w http.ResponseWriter, r *http.Request) {
floatingIP, err := h.service.GetFloatingIP(chi.URLParam(r, "floating_ip_id"))
if errors.Is(err, appnetwork.ErrFloatingIPNotFound) {
respond.Error(w, http.StatusNotFound, "floating IP not found")
return
}
if err != nil {
respond.Error(w, http.StatusInternalServerError, "floating IP lookup failed")
return
}

respond.JSON(w, http.StatusOK, floatingIPResponse{
FloatingIP: toFloatingIPDocument(floatingIP),
})
}

func (h Handler) deleteFloatingIP(w http.ResponseWriter, r *http.Request) {
err := h.service.DeleteFloatingIP(chi.URLParam(r, "floating_ip_id"))
if errors.Is(err, appnetwork.ErrFloatingIPNotFound) {
respond.Error(w, http.StatusNotFound, "floating IP not found")
return
}
if err != nil {
respond.Error(w, http.StatusInternalServerError, "floating IP delete failed")
return
}

w.WriteHeader(http.StatusNoContent)
}
84 changes: 84 additions & 0 deletions internal/api/network/floating_ip_dto.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package network

import appnetwork "github.com/JSYoo5B/SandStack/internal/app/network"

type createFloatingIPRequest struct {
FloatingIP createFloatingIPDocument `json:"floatingip"`
}

type createFloatingIPDocument struct {
Description string `json:"description"`
FloatingNetworkID string `json:"floating_network_id"`
FloatingIP string `json:"floating_ip_address"`
PortID string `json:"port_id"`
FixedIP string `json:"fixed_ip_address"`
SubnetID string `json:"subnet_id"`
ProjectID string `json:"project_id"`
TenantID string `json:"tenant_id"`
}

func (r createFloatingIPRequest) createFloatingIP() appnetwork.CreateFloatingIP {
projectID := r.FloatingIP.ProjectID
if projectID == "" {
projectID = r.FloatingIP.TenantID
}

return appnetwork.CreateFloatingIP{
Description: r.FloatingIP.Description,
FloatingNetworkID: r.FloatingIP.FloatingNetworkID,
FloatingIP: r.FloatingIP.FloatingIP,
PortID: r.FloatingIP.PortID,
FixedIP: r.FloatingIP.FixedIP,
SubnetID: r.FloatingIP.SubnetID,
ProjectID: projectID,
}
}

type floatingIPListResponse struct {
FloatingIPs []floatingIPDocument `json:"floatingips"`
}

type floatingIPResponse struct {
FloatingIP floatingIPDocument `json:"floatingip"`
}

type floatingIPDocument struct {
ID string `json:"id"`
Description string `json:"description"`
FloatingNetworkID string `json:"floating_network_id"`
FloatingIP string `json:"floating_ip_address"`
PortID string `json:"port_id"`
FixedIP string `json:"fixed_ip_address"`
TenantID string `json:"tenant_id"`
ProjectID string `json:"project_id"`
Status string `json:"status"`
RouterID string `json:"router_id"`
Tags []string `json:"tags"`
}

func toFloatingIPDocuments(
floatingIPs []appnetwork.FloatingIP,
) []floatingIPDocument {
documents := make([]floatingIPDocument, 0, len(floatingIPs))
for _, floatingIP := range floatingIPs {
documents = append(documents, toFloatingIPDocument(floatingIP))
}

return documents
}

func toFloatingIPDocument(floatingIP appnetwork.FloatingIP) floatingIPDocument {
return floatingIPDocument{
ID: floatingIP.ID,
Description: floatingIP.Description,
FloatingNetworkID: floatingIP.FloatingNetworkID,
FloatingIP: floatingIP.FloatingIP,
PortID: floatingIP.PortID,
FixedIP: floatingIP.FixedIP,
TenantID: floatingIP.TenantID,
ProjectID: floatingIP.ProjectID,
Status: floatingIP.Status,
RouterID: floatingIP.RouterID,
Tags: floatingIP.Tags,
}
}
111 changes: 111 additions & 0 deletions internal/api/network/floating_ip_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package network_test

import (
"net/http/httptest"
"testing"

"github.com/JSYoo5B/SandStack/internal/api/network"
"github.com/JSYoo5B/SandStack/internal/testhelper"
"github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/layer3/floatingips"
"github.com/stretchr/testify/suite"
)

type FloatingIPSuite struct {
suite.Suite
server *httptest.Server
}

func TestFloatingIPSuite(t *testing.T) {
suite.Run(t, new(FloatingIPSuite))
}

func (s *FloatingIPSuite) SetupTest() {
s.server = httptest.NewServer(
network.NewRouter(testhelper.DefaultConfig()),
)
}

func (s *FloatingIPSuite) TearDownTest() {
s.server.Close()
}

func (s *FloatingIPSuite) TestListFloatingIPs() {
list := s.listFloatingIPs()

s.Assert().Empty(list)
}

func (s *FloatingIPSuite) TestCreateFloatingIPThenListFloatingIPs() {
created := s.createFloatingIP("public")

list := s.listFloatingIPs()

s.Assert().NotEmpty(created.ID)
s.Assert().Equal("public", created.FloatingNetworkID)
s.Assert().Equal("203.0.113.20", created.FloatingIP)
s.Assert().Equal("DOWN", created.Status)
s.Require().Len(list, 1)
s.Assert().Equal(created.ID, list[0].ID)
}

func (s *FloatingIPSuite) TestGetFloatingIP() {
created := s.createFloatingIP("public")

found, err := floatingips.Get(
s.T().Context(),
testhelper.ServiceClient(s.server.URL),
created.ID,
).Extract()
s.Require().NoError(err)
s.Require().NotNil(found)

s.Assert().Equal(created.ID, found.ID)
s.Assert().Equal("public", found.FloatingNetworkID)
}

func (s *FloatingIPSuite) TestDeleteFloatingIP() {
created := s.createFloatingIP("public")

err := floatingips.Delete(
s.T().Context(),
testhelper.ServiceClient(s.server.URL),
created.ID,
).ExtractErr()
s.Require().NoError(err)

list := s.listFloatingIPs()

s.Assert().Empty(list)
}

func (s *FloatingIPSuite) listFloatingIPs() []floatingips.FloatingIP {
pages, err := floatingips.List(
testhelper.ServiceClient(s.server.URL),
floatingips.ListOpts{},
).AllPages(s.T().Context())
s.Require().NoError(err)

list, err := floatingips.ExtractFloatingIPs(pages)
s.Require().NoError(err)

return list
}

func (s *FloatingIPSuite) createFloatingIP(
floatingNetworkID string,
) *floatingips.FloatingIP {
created, err := floatingips.Create(
s.T().Context(),
testhelper.ServiceClient(s.server.URL),
floatingips.CreateOpts{
Description: "floating IP for tests",
FloatingNetworkID: floatingNetworkID,
FloatingIP: "203.0.113.20",
ProjectID: "demo",
},
).Extract()
s.Require().NoError(err)
s.Require().NotNil(created)

return created
}
60 changes: 60 additions & 0 deletions internal/api/network/l3_router.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package network

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

"github.com/JSYoo5B/SandStack/internal/api/respond"
appnetwork "github.com/JSYoo5B/SandStack/internal/app/network"
"github.com/go-chi/chi/v5"
)

func (h Handler) listRouters(w http.ResponseWriter, _ *http.Request) {
respond.JSON(w, http.StatusOK, routerListResponse{
Routers: toRouterDocuments(h.service.ListRouters()),
})
}

func (h Handler) createRouter(w http.ResponseWriter, r *http.Request) {
var request createRouterRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
respond.Error(w, http.StatusBadRequest, "invalid JSON request body")
return
}

router := h.service.CreateRouter(request.createRouter())
respond.JSON(w, http.StatusCreated, routerResponse{
Router: toRouterDocument(router),
})
}

func (h Handler) getRouter(w http.ResponseWriter, r *http.Request) {
router, err := h.service.GetRouter(chi.URLParam(r, "router_id"))
if errors.Is(err, appnetwork.ErrRouterNotFound) {
respond.Error(w, http.StatusNotFound, "router not found")
return
}
if err != nil {
respond.Error(w, http.StatusInternalServerError, "router lookup failed")
return
}

respond.JSON(w, http.StatusOK, routerResponse{
Router: toRouterDocument(router),
})
}

func (h Handler) deleteRouter(w http.ResponseWriter, r *http.Request) {
err := h.service.DeleteRouter(chi.URLParam(r, "router_id"))
if errors.Is(err, appnetwork.ErrRouterNotFound) {
respond.Error(w, http.StatusNotFound, "router not found")
return
}
if err != nil {
respond.Error(w, http.StatusInternalServerError, "router delete failed")
return
}

w.WriteHeader(http.StatusNoContent)
}
Loading
Loading