/* Copyright (c) 2017 VMware, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package simulator import ( "errors" "fmt" "net" "strconv" "strings" "github.com/vmware/govmomi/vim25/methods" "github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/soap" "github.com/vmware/govmomi/vim25/types" ) var ipPool = MustNewIpPool(&types.IpPool{ Id: 1, Name: "ip-pool", AvailableIpv4Addresses: 250, AvailableIpv6Addresses: 250, AllocatedIpv6Addresses: 0, AllocatedIpv4Addresses: 0, Ipv4Config: &types.IpPoolIpPoolConfigInfo{ Netmask: "10.10.10.255", Gateway: "10.10.10.1", SubnetAddress: "10.10.10.0", Range: "10.10.10.2#250", }, Ipv6Config: &types.IpPoolIpPoolConfigInfo{ Netmask: "2001:4860:0:2001::ff", Gateway: "2001:4860:0:2001::1", SubnetAddress: "2001:4860:0:2001::0", Range: "2001:4860:0:2001::2#250", }, }) // IpPoolManager implements a simple IP Pool manager in which all pools are shared // across different datacenters. type IpPoolManager struct { mo.IpPoolManager pools map[int32]*IpPool nextPoolId int32 } func (m *IpPoolManager) init(*Registry) { m.pools = map[int32]*IpPool{ 1: ipPool, } m.nextPoolId = 2 } func (m *IpPoolManager) CreateIpPool(req *types.CreateIpPool) soap.HasFault { body := &methods.CreateIpPoolBody{} id := m.nextPoolId var err error m.pools[id], err = NewIpPool(&req.Pool) if err != nil { body.Fault_ = Fault("", &types.RuntimeFault{}) return body } m.nextPoolId++ body.Res = &types.CreateIpPoolResponse{ Returnval: id, } return body } func (m *IpPoolManager) DestroyIpPool(req *types.DestroyIpPool) soap.HasFault { delete(m.pools, req.Id) return &methods.DestroyIpPoolBody{ Res: &types.DestroyIpPoolResponse{}, } } func (m *IpPoolManager) QueryIpPools(req *types.QueryIpPools) soap.HasFault { pools := []types.IpPool{} for i := int32(1); i < m.nextPoolId; i++ { if p, ok := m.pools[i]; ok { pools = append(pools, *p.config) } } return &methods.QueryIpPoolsBody{ Res: &types.QueryIpPoolsResponse{ Returnval: pools, }, } } func (m *IpPoolManager) UpdateIpPool(req *types.UpdateIpPool) soap.HasFault { body := &methods.UpdateIpPoolBody{} var pool *IpPool var err error var ok bool if pool, ok = m.pools[req.Pool.Id]; !ok { body.Fault_ = Fault("", &types.NotFoundFault{}) return body } if pool.config.AllocatedIpv4Addresses+pool.config.AllocatedIpv6Addresses != 0 { body.Fault_ = Fault("update a pool has been used is not supported", &types.RuntimeFault{}) return body } m.pools[req.Pool.Id], err = NewIpPool(&req.Pool) if err != nil { body.Fault_ = Fault(err.Error(), &types.RuntimeFault{}) return body } body.Res = &types.UpdateIpPoolResponse{} return body } func (m *IpPoolManager) AllocateIpv4Address(req *types.AllocateIpv4Address) soap.HasFault { body := &methods.AllocateIpv4AddressBody{} pool, ok := m.pools[req.PoolId] if !ok { body.Fault_ = Fault("", &types.InvalidArgument{}) return body } ip, err := pool.AllocateIPv4(req.AllocationId) if err != nil { body.Fault_ = Fault(err.Error(), &types.RuntimeFault{}) return body } body.Res = &types.AllocateIpv4AddressResponse{ Returnval: ip, } return body } func (m *IpPoolManager) AllocateIpv6Address(req *types.AllocateIpv6Address) soap.HasFault { body := &methods.AllocateIpv6AddressBody{} pool, ok := m.pools[req.PoolId] if !ok { body.Fault_ = Fault("", &types.InvalidArgument{}) return body } ip, err := pool.AllocateIpv6(req.AllocationId) if err != nil { body.Fault_ = Fault(err.Error(), &types.RuntimeFault{}) return body } body.Res = &types.AllocateIpv6AddressResponse{ Returnval: ip, } return body } func (m *IpPoolManager) ReleaseIpAllocation(req *types.ReleaseIpAllocation) soap.HasFault { body := &methods.ReleaseIpAllocationBody{} pool, ok := m.pools[req.PoolId] if !ok { body.Fault_ = Fault("", &types.InvalidArgument{}) return body } pool.ReleaseIpv4(req.AllocationId) pool.ReleaseIpv6(req.AllocationId) body.Res = &types.ReleaseIpAllocationResponse{} return body } func (m *IpPoolManager) QueryIPAllocations(req *types.QueryIPAllocations) soap.HasFault { body := &methods.QueryIPAllocationsBody{} pool, ok := m.pools[req.PoolId] if !ok { body.Fault_ = Fault("", &types.InvalidArgument{}) return body } body.Res = &types.QueryIPAllocationsResponse{} ipv4, ok := pool.ipv4Allocation[req.ExtensionKey] if ok { body.Res.Returnval = append(body.Res.Returnval, types.IpPoolManagerIpAllocation{ IpAddress: ipv4, AllocationId: req.ExtensionKey, }) } ipv6, ok := pool.ipv6Allocation[req.ExtensionKey] if ok { body.Res.Returnval = append(body.Res.Returnval, types.IpPoolManagerIpAllocation{ IpAddress: ipv6, AllocationId: req.ExtensionKey, }) } return body } var ( errNoIpAvailable = errors.New("no ip address available") errInvalidAllocation = errors.New("allocation id not recognized") ) type IpPool struct { config *types.IpPool ipv4Allocation map[string]string ipv6Allocation map[string]string ipv4Pool []string ipv6Pool []string } func MustNewIpPool(config *types.IpPool) *IpPool { pool, err := NewIpPool(config) if err != nil { panic(err) } return pool } func NewIpPool(config *types.IpPool) (*IpPool, error) { pool := &IpPool{ config: config, ipv4Allocation: make(map[string]string), ipv6Allocation: make(map[string]string), } return pool, pool.init() } func (p *IpPool) init() error { // IPv4 range if p.config.Ipv4Config != nil { ranges := strings.Split(p.config.Ipv4Config.Range, ",") for _, r := range ranges { sp := strings.Split(r, "#") if len(sp) != 2 { return fmt.Errorf("format of range should be ip#number; got %q", r) } ip := net.ParseIP(strings.TrimSpace(sp[0])).To4() if ip == nil { return fmt.Errorf("bad ip format: %q", sp[0]) } length, err := strconv.Atoi(sp[1]) if err != nil { return err } for i := 0; i < length; i++ { p.ipv4Pool = append(p.ipv4Pool, net.IPv4(ip[0], ip[1], ip[2], ip[3]+byte(i)).String()) } } } // IPv6 range if p.config.Ipv6Config != nil { ranges := strings.Split(p.config.Ipv6Config.Range, ",") for _, r := range ranges { sp := strings.Split(r, "#") if len(sp) != 2 { return fmt.Errorf("format of range should be ip#number; got %q", r) } ip := net.ParseIP(strings.TrimSpace(sp[0])).To16() if ip == nil { return fmt.Errorf("bad ip format: %q", sp[0]) } length, err := strconv.Atoi(sp[1]) if err != nil { return err } for i := 0; i < length; i++ { var ipv6 [16]byte copy(ipv6[:], ip) ipv6[15] += byte(i) p.ipv6Pool = append(p.ipv6Pool, net.IP(ipv6[:]).String()) } } } return nil } func (p *IpPool) AllocateIPv4(allocation string) (string, error) { if ip, ok := p.ipv4Allocation[allocation]; ok { return ip, nil } l := len(p.ipv4Pool) if l == 0 { return "", errNoIpAvailable } ip := p.ipv4Pool[l-1] p.config.AvailableIpv4Addresses-- p.config.AllocatedIpv4Addresses++ p.ipv4Pool = p.ipv4Pool[:l-1] p.ipv4Allocation[allocation] = ip return ip, nil } func (p *IpPool) ReleaseIpv4(allocation string) error { ip, ok := p.ipv4Allocation[allocation] if !ok { return errInvalidAllocation } delete(p.ipv4Allocation, allocation) p.config.AvailableIpv4Addresses++ p.config.AllocatedIpv4Addresses-- p.ipv4Pool = append(p.ipv4Pool, ip) return nil } func (p *IpPool) AllocateIpv6(allocation string) (string, error) { if ip, ok := p.ipv6Allocation[allocation]; ok { return ip, nil } l := len(p.ipv6Pool) if l == 0 { return "", errNoIpAvailable } ip := p.ipv6Pool[l-1] p.config.AvailableIpv6Addresses-- p.config.AllocatedIpv6Addresses++ p.ipv6Pool = p.ipv6Pool[:l-1] p.ipv6Allocation[allocation] = ip return ip, nil } func (p *IpPool) ReleaseIpv6(allocation string) error { ip, ok := p.ipv6Allocation[allocation] if !ok { return errInvalidAllocation } delete(p.ipv6Allocation, allocation) p.config.AvailableIpv6Addresses++ p.config.AllocatedIpv6Addresses-- p.ipv6Pool = append(p.ipv6Pool, ip) return nil }