azure-container-networking/cnm/ipam/ipam_test.go

434 строки
9.5 KiB
Go

// Copyright 2017 Microsoft. All rights reserved.
// MIT License
package ipam
import (
"bytes"
"encoding/json"
"fmt"
"net"
"net/http"
"net/http/httptest"
"net/url"
"os"
"strconv"
"testing"
"github.com/Azure/azure-container-networking/cnm"
"github.com/Azure/azure-container-networking/common"
"github.com/Azure/azure-container-networking/ipam"
)
var (
plugin IpamPlugin
mux *http.ServeMux
)
var (
ipamQueryUrl = "localhost:42424"
ipamQueryResponse = "" +
"<Interfaces>" +
" <Interface MacAddress=\"*\" IsPrimary=\"true\">" +
" <IPSubnet Prefix=\"10.0.0.0/16\">" +
" <IPAddress Address=\"10.0.0.4\" IsPrimary=\"true\"/>" +
" <IPAddress Address=\"10.0.0.5\" IsPrimary=\"false\"/>" +
" <IPAddress Address=\"10.0.0.6\" IsPrimary=\"false\"/>" +
" <IPAddress Address=\"10.0.0.7\" IsPrimary=\"false\"/>" +
" <IPAddress Address=\"10.0.0.8\" IsPrimary=\"false\"/>" +
" <IPAddress Address=\"10.0.0.9\" IsPrimary=\"false\"/>" +
" </IPSubnet>" +
" </Interface>" +
"</Interfaces>"
localAsId string
poolId1 string
address1 string
)
// Wraps the test run with plugin setup and teardown.
func TestMain(m *testing.M) {
var config common.PluginConfig
// Create a fake local agent to handle requests from IPAM plugin.
u, _ := url.Parse("tcp://" + ipamQueryUrl)
testAgent, err := common.NewListener(u)
if err != nil {
fmt.Printf("Failed to create agent, err:%v.\n", err)
return
}
testAgent.AddHandler("/", handleIpamQuery)
err = testAgent.Start(make(chan error, 1))
if err != nil {
fmt.Printf("Failed to start agent, err:%v.\n", err)
return
}
// Create the plugin.
plugin, err = NewPlugin(&config)
if err != nil {
fmt.Printf("Failed to create IPAM plugin, err:%v.\n", err)
return
}
// Configure test mode.
plugin.SetOption(common.OptEnvironment, common.OptEnvironmentAzure)
plugin.SetOption(common.OptAPIServerURL, "null")
plugin.SetOption(common.OptIpamQueryUrl, "http://"+ipamQueryUrl)
// Start the plugin.
err = plugin.Start(&config)
if err != nil {
fmt.Printf("Failed to start IPAM plugin, err:%v.\n", err)
return
}
// Get the internal http mux as test hook.
mux = plugin.(*ipamPlugin).Listener.GetMux()
// Run tests.
exitCode := m.Run()
// Cleanup.
plugin.Stop()
testAgent.Stop()
os.Exit(exitCode)
}
// Handles queries from IPAM source.
func handleIpamQuery(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(ipamQueryResponse))
}
// Decodes plugin's responses to test requests.
func decodeResponse(w *httptest.ResponseRecorder, response interface{}) error {
if w.Code != http.StatusOK {
return fmt.Errorf("Request failed with HTTP error %d", w.Code)
}
if w.Body == nil {
return fmt.Errorf("Response body is empty")
}
return json.NewDecoder(w.Body).Decode(&response)
}
//
// Libnetwork remote IPAM API compliance tests
// https://github.com/docker/libnetwork/blob/master/docs/ipam.md
//
// Tests Plugin.Activate functionality.
func TestActivate(t *testing.T) {
var resp cnm.ActivateResponse
req, err := http.NewRequest(http.MethodGet, "/Plugin.Activate", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
mux.ServeHTTP(w, req)
err = decodeResponse(w, &resp)
if err != nil || resp.Err != "" || resp.Implements[0] != "IpamDriver" {
t.Errorf("Activate response is invalid %+v", resp)
}
}
// Tests IpamDriver.GetCapabilities functionality.
func TestGetCapabilities(t *testing.T) {
var resp GetCapabilitiesResponse
req, err := http.NewRequest(http.MethodGet, GetCapabilitiesPath, nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
mux.ServeHTTP(w, req)
err = decodeResponse(w, &resp)
if err != nil || resp.Err != "" {
t.Errorf("GetCapabilities response is invalid %+v", resp)
}
}
// Tests IpamDriver.GetDefaultAddressSpaces functionality.
func TestGetDefaultAddressSpaces(t *testing.T) {
var resp GetDefaultAddressSpacesResponse
req, err := http.NewRequest(http.MethodGet, GetAddressSpacesPath, nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
mux.ServeHTTP(w, req)
err = decodeResponse(w, &resp)
if err != nil || resp.Err != "" || resp.LocalDefaultAddressSpace == "" {
t.Errorf("GetDefaultAddressSpaces response is invalid %+v", resp)
}
localAsId = resp.LocalDefaultAddressSpace
}
// Tests IpamDriver.RequestPool functionality.
func TestRequestPool(t *testing.T) {
var body bytes.Buffer
var resp RequestPoolResponse
payload := &RequestPoolRequest{
AddressSpace: localAsId,
}
json.NewEncoder(&body).Encode(payload)
req, err := http.NewRequest(http.MethodGet, RequestPoolPath, &body)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
mux.ServeHTTP(w, req)
err = decodeResponse(w, &resp)
if err != nil || resp.Err != "" {
t.Errorf("RequestPool response is invalid %+v", resp)
}
poolId1 = resp.PoolID
}
// Tests IpamDriver.RequestAddress functionality.
func TestRequestAddress(t *testing.T) {
var body bytes.Buffer
var resp RequestAddressResponse
payload := &RequestAddressRequest{
PoolID: poolId1,
Address: "",
Options: nil,
}
json.NewEncoder(&body).Encode(payload)
req, err := http.NewRequest(http.MethodGet, RequestAddressPath, &body)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
mux.ServeHTTP(w, req)
err = decodeResponse(w, &resp)
if err != nil || resp.Err != "" {
t.Errorf("RequestAddress response is invalid %+v", resp)
}
address, _, _ := net.ParseCIDR(resp.Address)
address1 = address.String()
}
// Tests IpamDriver.GetPoolInfo functionality.
func TestGetPoolInfo(t *testing.T) {
var body bytes.Buffer
var resp GetPoolInfoResponse
payload := &GetPoolInfoRequest{
PoolID: poolId1,
}
json.NewEncoder(&body).Encode(payload)
req, err := http.NewRequest(http.MethodGet, GetPoolInfoPath, &body)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
mux.ServeHTTP(w, req)
err = decodeResponse(w, &resp)
if err != nil || resp.Err != "" {
t.Errorf("GetPoolInfo response is invalid %+v", resp)
}
}
// Tests IpamDriver.ReleaseAddress functionality.
func TestReleaseAddress(t *testing.T) {
var body bytes.Buffer
var resp ReleaseAddressResponse
payload := &ReleaseAddressRequest{
PoolID: poolId1,
Address: address1,
}
json.NewEncoder(&body).Encode(payload)
req, err := http.NewRequest(http.MethodGet, ReleaseAddressPath, &body)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
mux.ServeHTTP(w, req)
err = decodeResponse(w, &resp)
if err != nil || resp.Err != "" {
t.Errorf("ReleaseAddress response is invalid %+v", resp)
}
}
// Tests IpamDriver.ReleasePool functionality.
func TestReleasePool(t *testing.T) {
var body bytes.Buffer
var resp ReleasePoolResponse
payload := &ReleasePoolRequest{
PoolID: poolId1,
}
json.NewEncoder(&body).Encode(payload)
req, err := http.NewRequest(http.MethodGet, ReleasePoolPath, &body)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
mux.ServeHTTP(w, req)
err = decodeResponse(w, &resp)
if err != nil || resp.Err != "" {
t.Errorf("ReleasePool response is invalid %+v", resp)
}
}
// Utility function to request address from IPAM.
func reqAddrInternal(payload *RequestAddressRequest) (string, error) {
var body bytes.Buffer
var resp RequestAddressResponse
json.NewEncoder(&body).Encode(payload)
req, err := http.NewRequest(http.MethodGet, RequestAddressPath, &body)
if err != nil {
return "", err
}
w := httptest.NewRecorder()
mux.ServeHTTP(w, req)
err = decodeResponse(w, &resp)
if err != nil {
return "", err
}
return resp.Address, nil
}
// Utility function to release address from IPAM.
func releaseAddrInternal(payload *ReleaseAddressRequest) error {
var body bytes.Buffer
var resp ReleaseAddressResponse
json.NewEncoder(&body).Encode(payload)
req, err := http.NewRequest(http.MethodGet, ReleaseAddressPath, &body)
if err != nil {
return err
}
w := httptest.NewRecorder()
mux.ServeHTTP(w, req)
err = decodeResponse(w, &resp)
if err != nil {
return err
}
return nil
}
// Tests IpamDriver.RequestAddress with id.
func TestRequestAddressWithID(t *testing.T) {
var ipList [2]string
for i := 0; i < 2; i++ {
payload := &RequestAddressRequest{
PoolID: poolId1,
Address: "",
Options: make(map[string]string),
}
payload.Options[ipam.OptAddressID] = "id" + strconv.Itoa(i)
addr1, err := reqAddrInternal(payload)
if err != nil {
t.Errorf("RequestAddress response is invalid %+v", err)
}
addr2, err := reqAddrInternal(payload)
if err != nil {
t.Errorf("RequestAddress response is invalid %+v", err)
}
if addr1 != addr2 {
t.Errorf("RequestAddress with id %+v doesn't match with retrieved addr %+v ", addr1, addr2)
}
address, _, _ := net.ParseCIDR(addr1)
ipList[i] = address.String()
}
for i := 0; i < 2; i++ {
payload := &ReleaseAddressRequest{
PoolID: poolId1,
Address: ipList[i],
}
err := releaseAddrInternal(payload)
if err != nil {
t.Errorf("ReleaseAddress response is invalid %+v", err)
}
}
}
// Tests IpamDriver.ReleaseAddress with id.
func TestReleaseAddressWithID(t *testing.T) {
reqPayload := &RequestAddressRequest{
PoolID: poolId1,
Address: "",
Options: make(map[string]string),
}
reqPayload.Options[ipam.OptAddressID] = "id1"
_, err := reqAddrInternal(reqPayload)
if err != nil {
t.Errorf("RequestAddress response is invalid %+v", err)
}
releasePayload := &ReleaseAddressRequest{
PoolID: poolId1,
Address: "",
Options: make(map[string]string),
}
releasePayload.Options[ipam.OptAddressID] = "id1"
err = releaseAddrInternal(releasePayload)
if err != nil {
t.Errorf("ReleaseAddress response is invalid %+v", err)
}
}