Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #32 from cmingxu/ipam-new
Ipam new
- Loading branch information
Showing
12 changed files
with
1,070 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,4 @@ coverage-all.out | |
coverage.out | ||
coverage.html | ||
.bolt.db | ||
.bolt-foobar.db |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
package ipam | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"net/http" | ||
|
||
"github.com/Dataman-Cloud/swan/api/utils" | ||
ipamanger "github.com/Dataman-Cloud/swan/ipam" | ||
) | ||
|
||
func (r *Router) AllocateIP(w http.ResponseWriter, req *http.Request) error { | ||
if err := utils.CheckForJSON(req); err != nil { | ||
return err | ||
} | ||
|
||
if err := req.ParseForm(); err != nil { | ||
return err | ||
} | ||
|
||
ipStr := req.Form.Get("ip") | ||
if ipStr == "" { | ||
return errors.New("no ip specified") | ||
} | ||
|
||
ip, err := r.ipam.AllocateIp(ipamanger.IP{Ip: ipStr}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return json.NewEncoder(w).Encode(ip) | ||
} | ||
|
||
func (r *Router) AllocateNextAvailable(w http.ResponseWriter, req *http.Request) error { | ||
if err := utils.CheckForJSON(req); err != nil { | ||
return err | ||
} | ||
|
||
ip, err := r.ipam.AllocateNextAvailableIP() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return json.NewEncoder(w).Encode(ip) | ||
} | ||
|
||
func (r *Router) ListAvailableIps(w http.ResponseWriter, req *http.Request) error { | ||
if err := utils.CheckForJSON(req); err != nil { | ||
return err | ||
} | ||
|
||
list, err := r.ipam.IPsAvailable() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return json.NewEncoder(w).Encode(list) | ||
} | ||
|
||
func (r *Router) ListAllocatedIps(w http.ResponseWriter, req *http.Request) error { | ||
if err := utils.CheckForJSON(req); err != nil { | ||
return err | ||
} | ||
|
||
list, err := r.ipam.IPsAllocated() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return json.NewEncoder(w).Encode(list) | ||
} | ||
|
||
func (r *Router) ReleaseIP(w http.ResponseWriter, req *http.Request) error { | ||
if err := utils.CheckForJSON(req); err != nil { | ||
return err | ||
} | ||
|
||
if err := req.ParseForm(); err != nil { | ||
return err | ||
} | ||
|
||
var param struct { | ||
IP string `json:"ip"` | ||
} | ||
|
||
decoder := json.NewDecoder(req.Body) | ||
if err := decoder.Decode(¶m); err != nil { | ||
return err | ||
} | ||
|
||
err := r.ipam.Release(ipamanger.IP{Ip: param.IP}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (r *Router) RefillIPs(w http.ResponseWriter, req *http.Request) error { | ||
if err := utils.CheckForJSON(req); err != nil { | ||
return err | ||
} | ||
|
||
if err := req.ParseForm(); err != nil { | ||
return err | ||
} | ||
|
||
var param struct { | ||
IPs []string `json:"ips"` | ||
} | ||
|
||
var ips []ipamanger.IP | ||
decoder := json.NewDecoder(req.Body) | ||
if err := decoder.Decode(¶m); err != nil { | ||
return err | ||
} | ||
|
||
for _, ipStr := range param.IPs { | ||
ips = append(ips, ipamanger.IP{Ip: ipStr}) | ||
} | ||
|
||
err := r.ipam.Refill(ips) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (r *Router) ListIPs(w http.ResponseWriter, req *http.Request) error { | ||
if err := utils.CheckForJSON(req); err != nil { | ||
return err | ||
} | ||
|
||
list, err := r.ipam.AllIPs() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return json.NewEncoder(w).Encode(list) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package ipam | ||
|
||
import ( | ||
"github.com/Dataman-Cloud/swan/api/router" | ||
manager "github.com/Dataman-Cloud/swan/ipam" | ||
) | ||
|
||
type Router struct { | ||
routes []*router.Route | ||
ipam *manager.IPAM | ||
} | ||
|
||
// NewRouter initializes a new ipam router. | ||
func NewRouter(manager *manager.IPAM) *Router { | ||
r := &Router{ | ||
ipam: manager, | ||
} | ||
|
||
r.initRoutes() | ||
return r | ||
} | ||
|
||
func (r *Router) Routes() []*router.Route { | ||
return r.routes | ||
} | ||
|
||
func (r *Router) initRoutes() { | ||
r.routes = []*router.Route{ | ||
router.NewRoute("GET", "/v1/ipam/allocate_randomly", r.AllocateNextAvailable), | ||
router.NewRoute("GET", "/v1/ipam/allocated_ips", r.ListAllocatedIps), | ||
router.NewRoute("GET", "/v1/ipam/available_ips", r.ListAvailableIps), | ||
router.NewRoute("POST", "/v1/ipam/release", r.ReleaseIP), | ||
router.NewRoute("GET", "/v1/ipam/allocate", r.AllocateIP), | ||
router.NewRoute("POST", "/v1/ipam/ips", r.RefillIPs), | ||
router.NewRoute("GET", "/v1/ipam/ips", r.ListIPs), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# IP Address Manager | ||
The IPAM for Swan is supposed to manage lifecycle of a predefined group | ||
of IPs, all IP addresses should be avaliable within the same layer 2 | ||
subnet as well as the hosts, all of them are reserved for containers, | ||
each container could be assigned a unique ip, with underlaying Macvlan | ||
bridge created by docker. | ||
|
||
## This is not a docker plugin | ||
The initial thought would be make this as a docker plugin like DHCP | ||
IPAM, so docker daemon could reach the IPAM remotely to where it stay in the Swan | ||
managers. But the truth is that as we have our own scheduler by default so this | ||
IPAM was not intend to run in standlone mode without scheduler, as though the only consumer of | ||
the IPAM would be the schdueler itself, it might be better choice make | ||
the IPAM not a plugin but part of scheduler which can access both from | ||
HTTP API and call directly. | ||
|
||
## How to initialize the IP list pool | ||
IP list pool supposed to be entered mannuly through HTTP API, which | ||
each ip should be unique and accessible within the same layer 2 subnet. | ||
|
||
## Lifecycle of a ip | ||
|
||
* `avaliable` avaliable to be allocate to a container | ||
* `reserved` reserved, should not allocated to any container | ||
* `allocated` currently used by a container | ||
* `releasing` released but not avaliable soon, will be turn into | ||
avaliable state after certain time periods | ||
|
||
## How to interact with IPAM, the APIs | ||
|
||
* `list` avaliable ips, no matter what state they are | ||
* `initialize` the ip pool | ||
* `empty` the ip pool | ||
* `allocate` the ip from pool | ||
* `release` a ip back to the pool | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"ips": ["192.168.1.2", "192.168.1.3", "192.168.1.4", "192.168.1.5", "192.168.1.6", "192.168.1.7", "192.168.1.8", "192.168.1.9", "192.168.1.10", "192.168.1.11", "192.168.1.12", "192.168.1.13", "192.168.1.14", "192.168.1.15", "192.168.1.16", "192.168.1.17", "192.168.1.18", "192.168.1.19", "192.168.1.20", "192.168.1.21", "192.168.1.22", "192.168.1.23", "192.168.1.24", "192.168.1.25", "192.168.1.26", "192.168.1.27", "192.168.1.28", "192.168.1.29", "192.168.1.30", "192.168.1.31", "192.168.1.32", "192.168.1.33", "192.168.1.34", "192.168.1.35", "192.168.1.36", "192.168.1.37", "192.168.1.38", "192.168.1.39", "192.168.1.40", "192.168.1.41", "192.168.1.42", "192.168.1.43", "192.168.1.44", "192.168.1.45", "192.168.1.46", "192.168.1.47", "192.168.1.48", "192.168.1.49", "192.168.1.50", "192.168.1.51", "192.168.1.52", "192.168.1.53", "192.168.1.54", "192.168.1.55", "192.168.1.56", "192.168.1.57", "192.168.1.58", "192.168.1.59", "192.168.1.60", "192.168.1.61", "192.168.1.62", "192.168.1.63", "192.168.1.64", "192.168.1.65", "192.168.1.66", "192.168.1.67", "192.168.1.68", "192.168.1.69", "192.168.1.70", "192.168.1.71", "192.168.1.72", "192.168.1.73", "192.168.1.74", "192.168.1.75", "192.168.1.76", "192.168.1.77", "192.168.1.78", "192.168.1.79", "192.168.1.80", "192.168.1.81", "192.168.1.82", "192.168.1.83", "192.168.1.84", "192.168.1.85", "192.168.1.86", "192.168.1.87", "192.168.1.88", "192.168.1.89", "192.168.1.90", "192.168.1.91", "192.168.1.92", "192.168.1.93", "192.168.1.94", "192.168.1.95", "192.168.1.96", "192.168.1.97", "192.168.1.98", "192.168.1.99", "192.168.1.100", "192.168.1.101", "192.168.1.102", "192.168.1.103", "192.168.1.104", "192.168.1.105", "192.168.1.106", "192.168.1.107", "192.168.1.108", "192.168.1.109", "192.168.1.110", "192.168.1.111", "192.168.1.112", "192.168.1.113", "192.168.1.114", "192.168.1.115", "192.168.1.116", "192.168.1.117", "192.168.1.118", "192.168.1.119", "192.168.1.120", "192.168.1.121", "192.168.1.122", "192.168.1.123", "192.168.1.124", "192.168.1.125", "192.168.1.126", "192.168.1.127", "192.168.1.128", "192.168.1.129", "192.168.1.130", "192.168.1.131", "192.168.1.132", "192.168.1.133", "192.168.1.134", "192.168.1.135", "192.168.1.136", "192.168.1.137", "192.168.1.138", "192.168.1.139", "192.168.1.140", "192.168.1.141", "192.168.1.142", "192.168.1.143", "192.168.1.144", "192.168.1.145", "192.168.1.146", "192.168.1.147", "192.168.1.148", "192.168.1.149", "192.168.1.150", "192.168.1.151", "192.168.1.152", "192.168.1.153", "192.168.1.154", "192.168.1.155", "192.168.1.156", "192.168.1.157", "192.168.1.158", "192.168.1.159", "192.168.1.160", "192.168.1.161", "192.168.1.162", "192.168.1.163", "192.168.1.164", "192.168.1.165", "192.168.1.166", "192.168.1.167", "192.168.1.168", "192.168.1.169", "192.168.1.170", "192.168.1.171", "192.168.1.172", "192.168.1.173", "192.168.1.174", "192.168.1.175", "192.168.1.176", "192.168.1.177", "192.168.1.178", "192.168.1.179", "192.168.1.180", "192.168.1.181", "192.168.1.182", "192.168.1.183", "192.168.1.184", "192.168.1.185", "192.168.1.186", "192.168.1.187", "192.168.1.188", "192.168.1.189", "192.168.1.190", "192.168.1.191", "192.168.1.192", "192.168.1.193", "192.168.1.194", "192.168.1.195", "192.168.1.196", "192.168.1.197", "192.168.1.198", "192.168.1.199", "192.168.1.200", "192.168.1.201", "192.168.1.202", "192.168.1.203", "192.168.1.204", "192.168.1.205", "192.168.1.206", "192.168.1.207", "192.168.1.208", "192.168.1.209", "192.168.1.210", "192.168.1.211", "192.168.1.212", "192.168.1.213", "192.168.1.214", "192.168.1.215", "192.168.1.216", "192.168.1.217", "192.168.1.218", "192.168.1.219", "192.168.1.220", "192.168.1.221", "192.168.1.222", "192.168.1.223", "192.168.1.224", "192.168.1.225", "192.168.1.226", "192.168.1.227", "192.168.1.228", "192.168.1.229", "192.168.1.230", "192.168.1.231", "192.168.1.232", "192.168.1.233", "192.168.1.234", "192.168.1.235", "192.168.1.236", "192.168.1.237", "192.168.1.238", "192.168.1.239", "192.168.1.240", "192.168.1.241", "192.168.1.242", "192.168.1.243", "192.168.1.244", "192.168.1.245", "192.168.1.246", "192.168.1.247", "192.168.1.248", "192.168.1.249", "192.168.1.250", "192.168.1.251", "192.168.1.252", "192.168.1.253", "192.168.1.254"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package ipam | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"net" | ||
"strconv" | ||
"strings" | ||
"time" | ||
) | ||
|
||
const ( | ||
IP_STATE_AVAILABLE = "available" | ||
IP_STATE_ALLOCATED = "allocated" | ||
) | ||
|
||
var ( | ||
ErrParseIp = errors.New("ip parse IP") | ||
ErrIsMultiCastIP = errors.New("ip is a multicast IP") | ||
ErrIsLoopbackIP = errors.New("ip is a loopback IP") | ||
ErrIsUnspecified = errors.New("ip is unspecified") | ||
ErrIsLinkLocalUnicast = errors.New("ip is link local unicast") | ||
) | ||
|
||
// see `https://golang.org/src/net/ip.go` | ||
|
||
type IP struct { | ||
Ip string `json:"Ip"` | ||
State string `json:"State"` | ||
ReleaseAt time.Time `json:"ReleaseAt"` | ||
TaskId string `json:"TaskId"` | ||
} | ||
|
||
func NewIpFromIp(ip string) (IP, error) { | ||
if err := validIp(ip); err != nil { | ||
return IP{}, err | ||
} | ||
|
||
return IP{ | ||
Ip: ip, | ||
State: IP_STATE_AVAILABLE, | ||
}, nil | ||
} | ||
|
||
func validIp(ipString string) error { | ||
ip := net.ParseIP(ipString) | ||
|
||
if ip == nil { | ||
return ErrParseIp | ||
} | ||
|
||
if ip.IsUnspecified() { | ||
return ErrIsUnspecified | ||
} | ||
|
||
if ip.IsLoopback() { | ||
return ErrIsLoopbackIP | ||
} | ||
|
||
if ip.IsLinkLocalUnicast() { | ||
return ErrIsLinkLocalUnicast | ||
} | ||
|
||
if ip.IsMulticast() { | ||
return ErrIsMultiCastIP | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (ip IP) ToIP() net.IP { | ||
ipv4 := net.ParseIP(ip.Ip) | ||
return ipv4 | ||
} | ||
|
||
func (ip IP) ToString() string { | ||
return fmt.Sprintf("ip<%s> state<%s> taskId<%s>", ip.Ip, ip.State, ip.TaskId) | ||
} | ||
|
||
func (ip IP) ToInteger() int64 { | ||
ip4 := ip.ToIP().To4() | ||
bin := make([]string, len(ip4)) | ||
for i, v := range ip4 { | ||
bin[i] = fmt.Sprintf("%08b", v) | ||
} | ||
i, _ := strconv.ParseInt(strings.Join(bin, ""), 2, 64) | ||
|
||
return i | ||
} | ||
|
||
func (ip IP) Key() string { | ||
return ip.Ip | ||
} | ||
|
||
type IPList []IP | ||
|
||
func (s IPList) Len() int { | ||
return len(s) | ||
} | ||
func (s IPList) Swap(i, j int) { | ||
s[i], s[j] = s[j], s[i] | ||
} | ||
func (s IPList) Less(i, j int) bool { | ||
return s[i].ToInteger() < s[j].ToInteger() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package ipam | ||
|
||
import ( | ||
"net" | ||
"sort" | ||
"strconv" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestIpToString(t *testing.T) { | ||
ip := IP{Ip: "127.0.0.1", State: IP_STATE_AVAILABLE, TaskId: "task-id"} | ||
assert.Equal(t, ip.ToString(), "ip<127.0.0.1> state<available> taskId<task-id>") | ||
} | ||
|
||
func TestIpKey(t *testing.T) { | ||
ip := IP{Ip: "127.0.0.1", State: IP_STATE_AVAILABLE, TaskId: "task-id"} | ||
assert.Equal(t, "127.0.0.1", ip.Key()) | ||
} | ||
|
||
func TestToIP(t *testing.T) { | ||
ip := IP{Ip: "127.0.0.1", State: IP_STATE_AVAILABLE, TaskId: "task-id"} | ||
ipExpected := net.ParseIP("127.0.0.1") | ||
assert.Equal(t, ipExpected, ip.ToIP()) | ||
} | ||
|
||
func TestToInteger(t *testing.T) { | ||
ip := IP{Ip: "127.0.0.1", State: IP_STATE_AVAILABLE, TaskId: "task-id"} | ||
i, _ := strconv.ParseInt("01111111000000000000000000000001", 2, 64) | ||
assert.Equal(t, i, ip.ToInteger()) | ||
} | ||
|
||
func TestIPListSort(t *testing.T) { | ||
ip1 := IP{Ip: "127.0.0.1", State: IP_STATE_AVAILABLE, TaskId: "task-id"} | ||
ip2 := IP{Ip: "127.0.0.2", State: IP_STATE_AVAILABLE, TaskId: "task-id"} | ||
|
||
ips := IPList([]IP{ip2, ip1}) | ||
assert.Equal(t, 2, len(ips)) | ||
assert.Equal(t, ip2, ips[0]) | ||
sort.Sort(ips) | ||
assert.Equal(t, ip1, ips[0]) | ||
} | ||
|
||
func TestValidIp(t *testing.T) { | ||
nilIP := "foobar" | ||
assert.Equal(t, ErrParseIp, validIp(nilIP)) | ||
|
||
unspeficiedIP := "0.0.0.0" | ||
assert.Equal(t, ErrIsUnspecified, validIp(unspeficiedIP)) | ||
|
||
loobackIP := "127.0.0.1" | ||
assert.Equal(t, ErrIsLoopbackIP, validIp(loobackIP)) | ||
|
||
linkLocalUnicast := "169.254.1.1" | ||
assert.Equal(t, ErrIsLinkLocalUnicast, validIp(linkLocalUnicast)) | ||
|
||
multicast := "224.0.0.0" | ||
assert.Equal(t, ErrIsMultiCastIP, validIp(multicast)) | ||
|
||
validIpStr := "192.168.1.2" | ||
assert.Nil(t, validIp(validIpStr)) | ||
|
||
} |
Oops, something went wrong.