Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Task] Support the new API to get the port status of ovs. #280

Merged
merged 11 commits into from
Aug 28, 2018
Merged
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
43 changes: 42 additions & 1 deletion API.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@
- [List Namespaces](#list-namespaces)
- [Get Namespace](#get-namespace)
- [Delete Namespace](#delete-namespace)

- [OVS](#ovs)
- [Get PortStats](#get-portstats)



## User
Expand Down Expand Up @@ -1633,3 +1635,42 @@ Response Data:
"message": "Delete success"
}
```

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

格式有錯?

## OVS
In the ovs api, we should use two parameter to indicate what OVS we want to operate in.
1. NodeName: the node name in the kubernetes cluster
2. BridgeName: the bridge name when admin create the network in the network page.
the portal can use the list network to fetch the actual bridge name of each network.

### Get PortStats

**GET /v1/ovs/portstats/?nodeName=xxx&bridge=xxx**

Example:

```
curl http://localhost:7890/v1/ovs/portstats?nodeName=vortex-dev&bridgeName=system-47f8ce
```

Response Data:

```json=
{
{
"PortID": 2,
"received": {
"packets": 0,
"bytes": 0,
"dropped": 0,
"errors": 0
},
"traansmitted": {
"packets": 8,
"bytes": 648,
"dropped": 0,
"errors": 0,
"collisions": 0
}
}
}
```
28 changes: 28 additions & 0 deletions src/entity/ovs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package entity

// PortStatsReceive contains information regarding the number of received
// packets, bytes, etc.
type PortStatsReceive struct {
Packets uint64 `json:"packets"`
Bytes uint64 `json:"bytes"`
Dropped uint64 `json:"dropped"`
Errors uint64 `json:"errors"`
Frame uint64 `json:"-"`
Over uint64 `json:"-"`
CRC uint64 `json:"-"`
}

// PortStatsTransmit contains information regarding the number of transmitted
// packets, bytes, etc.
type PortStatsTransmit struct {
Packets uint64 `json:"packets"`
Bytes uint64 `json:"bytes"`
Dropped uint64 `json:"dropped"`
Errors uint64 `json:"errors"`
Collisions uint64 `json:"collisions"`
}
type OVSPortStat struct {
PortID uint32 `json:'portID"`
Received PortStatsReceive `json:"received"`
Transmitted PortStatsTransmit `json:"traansmitted"`
}
21 changes: 21 additions & 0 deletions src/networkcontroller/network_controller.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package networkcontroller

import (
"bytes"
"encoding/binary"
"time"

pb "github.com/linkernetworks/network-controller/messages"
Expand Down Expand Up @@ -127,3 +129,22 @@ func (nc *NetworkController) DeleteOVSNetwork(bridgeName string) error {
}
return nil
}

// DumpOVSPorts will dump ports infromation of the target ovs
func (nc *NetworkController) DumpOVSPorts(bridgeName string) ([]entity.OVSPortStat, error) {
data, err := nc.ClientCtl.DumpPorts(
nc.Context,
&pb.DumpPortsRequest{
BridgeName: bridgeName,
})
if err != nil {
return nil, err
}

ovsPortStats := make([]entity.OVSPortStat, len(data.Ports))
for i, p := range data.Ports {
buf := bytes.NewBuffer(p)
err = binary.Read(buf, binary.BigEndian, &ovsPortStats[i])
}
return ovsPortStats, nil
}
3 changes: 3 additions & 0 deletions src/networkcontroller/network_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@ func (suite *NetworkControllerTestSuite) TestCreateOVSUserpsaceNetwork() {
err = nc.CreateOVSNetwork("netdev", tName, network.Nodes[0].PhyInterfaces, network.VlanTags)
suite.NoError(err)

data, err := nc.DumpOVSPorts(tName)
suite.NoError(err)
suite.Equal(2, len(data))
//TODO we need support the list function to check the ovs is existed
defer exec.Command("ovs-vsctl", "del-br", tName).Run()
}
Expand Down
21 changes: 21 additions & 0 deletions src/ovscontroller/ovs_system.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package ovscontroller

import (
"net"

"github.com/linkernetworks/vortex/src/entity"
"github.com/linkernetworks/vortex/src/networkcontroller"
"github.com/linkernetworks/vortex/src/serviceprovider"
)

func DumpPorts(sp *serviceprovider.Container, nodeName string, bridgeName string) ([]entity.OVSPortStat, error) {
nodeIP, err := sp.KubeCtl.GetNodeInternalIP(nodeName)
if err != nil {
return nil, err
}

nodeAddr := net.JoinHostPort(nodeIP, networkcontroller.DEFAULT_CONTROLLER_PORT)
nc, err := networkcontroller.New(nodeAddr)

return nc.DumpOVSPorts(bridgeName)
}
100 changes: 100 additions & 0 deletions src/ovscontroller/ovs_system_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package ovscontroller

import (
"bytes"
"fmt"
"math/rand"
"os"
"os/exec"
"runtime"
"testing"
"time"

"github.com/linkernetworks/vortex/src/config"
_ "github.com/linkernetworks/vortex/src/entity"
kc "github.com/linkernetworks/vortex/src/kubernetes"
"github.com/linkernetworks/vortex/src/serviceprovider"

"github.com/moby/moby/pkg/namesgenerator"
"github.com/stretchr/testify/suite"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
fakeclientset "k8s.io/client-go/kubernetes/fake"
)

const OVS_LOCAL_IP = "127.0.0.1"

func init() {
rand.Seed(time.Now().UnixNano())
}

func execute(suite *suite.Suite, cmd *exec.Cmd) {
w := bytes.NewBuffer(nil)
cmd.Stderr = w
err := cmd.Run()
suite.NoError(err)
fmt.Printf("Stderr: %s\n", string(w.Bytes()))
}

type OVSControllerTestSuite struct {
suite.Suite
sp *serviceprovider.Container
nodeName string
bridgeName string
}

func (suite *OVSControllerTestSuite) SetupSuite() {
cf := config.MustRead("../../config/testing.json")
suite.sp = serviceprovider.NewForTesting(cf)

// init fakeclient
fakeclient := fakeclientset.NewSimpleClientset()
suite.sp.KubeCtl = kc.New(fakeclient)

suite.bridgeName = namesgenerator.GetRandomName(0)[0:6]

// Create a fake clinet
// Initial nodes
suite.nodeName = namesgenerator.GetRandomName(0)
_, err := suite.sp.KubeCtl.Clientset.CoreV1().Nodes().Create(&corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: suite.nodeName,
},
Status: corev1.NodeStatus{
Addresses: []corev1.NodeAddress{
{
Type: "InternalIP",
Address: OVS_LOCAL_IP,
},
},
},
})
suite.NoError(err)

execute(&suite.Suite, exec.Command("ovs-vsctl", "add-br", suite.bridgeName))
}

func (suite *OVSControllerTestSuite) TearDownSuite() {
defer exec.Command("ovs-vsctl", "del-br", suite.bridgeName).Run()
}

func TestOVSNetworkSuite(t *testing.T) {
if runtime.GOOS != "linux" {
fmt.Println("We only testing the ovs function on Linux Host")
t.Skip()
return
}
if _, defined := os.LookupEnv("TEST_GRPC"); !defined {
t.SkipNow()
return
}
suite.Run(t, new(OVSControllerTestSuite))
}

// OK
func (suite *OVSControllerTestSuite) TestDumpOVSPorts() {
portStats, err := DumpPorts(suite.sp, suite.nodeName, suite.bridgeName)
suite.NoError(err)
suite.Equal(1, len(portStats))
}
36 changes: 36 additions & 0 deletions src/server/handler_ovs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package server

import (
"fmt"

response "github.com/linkernetworks/vortex/src/net/http"
"github.com/linkernetworks/vortex/src/net/http/query"
"github.com/linkernetworks/vortex/src/ovscontroller"
"github.com/linkernetworks/vortex/src/web"
)

func getOVSPortStatsHandler(ctx *web.Context) {
sp, req, resp := ctx.ServiceProvider, ctx.Request, ctx.Response

//Get the parameter
query := query.New(req.Request.URL.Query())
nodeName, exist := query.Str("nodeName")
if !exist {
response.BadRequest(req.Request, resp.ResponseWriter, fmt.Errorf("The nodeName must not be empty"))
return
}

bridgeName, exist := query.Str("bridgeName")
if !exist {
response.BadRequest(req.Request, resp.ResponseWriter, fmt.Errorf("The bridgeName must not be empty"))
return
}

fmt.Println(nodeName, bridgeName)
portStats, err := ovscontroller.DumpPorts(sp, nodeName, bridgeName)
if err != nil {
response.InternalServerError(req.Request, resp.ResponseWriter, err)
return
}
resp.WriteEntity(portStats)
}
74 changes: 74 additions & 0 deletions src/server/handler_ovs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package server

import (
_ "encoding/json"
"math/rand"
"net/http"
"net/http/httptest"

"time"

restful "github.com/emicklei/go-restful"
"github.com/linkernetworks/mongo"
"github.com/linkernetworks/vortex/src/config"
"github.com/linkernetworks/vortex/src/entity"
"github.com/linkernetworks/vortex/src/serviceprovider"
_ "github.com/moby/moby/pkg/namesgenerator"
"github.com/stretchr/testify/suite"
_ "gopkg.in/mgo.v2/bson"
//corev1 "k8s.io/api/core/v1"

"testing"
)

func init() {
rand.Seed(time.Now().UnixNano())
}

type OVSTestSuite struct {
suite.Suite
sp *serviceprovider.Container
wc *restful.Container
session *mongo.Session
storage entity.Storage
}

func (suite *OVSTestSuite) SetupSuite() {
cf := config.MustRead("../../config/testing.json")
sp := serviceprovider.NewForTesting(cf)

suite.sp = sp
//init session
suite.session = sp.Mongo.NewSession()
//init restful container
suite.wc = restful.NewContainer()
service := newOVSService(suite.sp)
suite.wc.Add(service)
}

func (suite *OVSTestSuite) TearDownSuite() {
}

func TestOVSSuite(t *testing.T) {
suite.Run(t, new(OVSTestSuite))
}

func (suite *OVSTestSuite) TestGetOVSPortStatsFail() {
//Empty data
httpRequest, err := http.NewRequest("GET", "http://localhost:7890/v1/ovs/portstat", nil)
suite.NoError(err)

httpWriter := httptest.NewRecorder()
suite.wc.Dispatch(httpWriter, httpRequest)
assertResponseCode(suite.T(), http.StatusBadRequest, httpWriter)

httpRequest, err = http.NewRequest("GET", "http://localhost:7890/v1/ovs/portstat?nodeName=11", nil)
httpWriter = httptest.NewRecorder()
suite.wc.Dispatch(httpWriter, httpRequest)
assertResponseCode(suite.T(), http.StatusBadRequest, httpWriter)

httpRequest, err = http.NewRequest("GET", "http://localhost:7890/v1/ovs/portstat?nodeName=11&&bridgeName=111", nil)
httpWriter = httptest.NewRecorder()
suite.wc.Dispatch(httpWriter, httpRequest)
assertResponseCode(suite.T(), http.StatusInternalServerError, httpWriter)
}
8 changes: 8 additions & 0 deletions src/server/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func (a *App) AppRoute() *mux.Router {
container.Add(newNamespaceService(a.ServiceProvider))
container.Add(newMonitoringService(a.ServiceProvider))
container.Add(newAppService(a.ServiceProvider))
container.Add(newOVSService(a.ServiceProvider))

router.PathPrefix("/v1/").Handler(container)
return router
Expand Down Expand Up @@ -170,3 +171,10 @@ func newMonitoringService(sp *serviceprovider.Container) *restful.WebService {
webService.Route(webService.GET("/controllers/{id}").To(handler.RESTfulServiceHandler(sp, getControllerMetricsHandler)))
return webService
}

func newOVSService(sp *serviceprovider.Container) *restful.WebService {
webService := new(restful.WebService)
webService.Path("/v1/ovs").Consumes(restful.MIME_JSON, restful.MIME_JSON).Produces(restful.MIME_JSON, restful.MIME_JSON)
webService.Route(webService.GET("/portstat").To(handler.RESTfulServiceHandler(sp, getOVSPortStatsHandler)))
return webService
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
1 change: 1 addition & 0 deletions tests/07-ovs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.json
4 changes: 4 additions & 0 deletions tests/07-ovs/credential
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"username":"[email protected]",
"password":"p@ssw0rd"
}
Loading