Permalink
Browse files

Provide a pluggable interface method that can be used to implement yo…

…ur own algorithms

Summary:
Added a new `NewDHCPBalancingAlgorithm` method to the `ConfigProvider` interface.
This allows users to implement their own special algorithms, w/o necessarily having to share
the code publicly.

Reviewed By: lsiudut, insomniacslk

Differential Revision: D6171748

fbshipit-source-id: d23a15fd34082ebb0fde594652c5e3d983698c9d
  • Loading branch information...
pallotron authored and facebook-github-bot committed Oct 31, 2017
1 parent 91e9af5 commit df7f94916c17c698daa7b2821896a54b566fb094
Showing with 121 additions and 72 deletions.
  1. +8 −0 config_provider.go
  2. +14 −3 docs/getting-started.md
  3. +28 −11 lib/config.go
  4. +9 −10 lib/dhcp_server.go
  5. +2 −2 lib/handler.go
  6. +12 −6 lib/interface.go
  7. +14 −10 lib/modulo.go
  8. +3 −3 lib/modulo_test.go
  9. +7 −7 lib/packet6.go
  10. +3 −3 lib/packet6_test.go
  11. +14 −10 lib/rr.go
  12. +3 −3 lib/rr_test.go
  13. +2 −2 lib/server.go
  14. +2 −2 lib/update_servers.go
View
@@ -43,3 +43,11 @@ func (h DefaultConfigProvider) NewHostSourcer(sourcerType, args string, version
func (h DefaultConfigProvider) ParseExtras(data json.RawMessage) (interface{}, error) {
return nil, nil
}
// NewDHCPBalancingAlgorithm returns a DHCPBalancingAlgorithm implementation.
// This can be used if you need to create your own balancing algorithm and
// integrate it with your infra without necesarily having to realase your code
// to github.
func (h DefaultConfigProvider) NewDHCPBalancingAlgorithm() (DHCPBalancingAlgorithm, error) {
return nil, nil
}
View
@@ -53,7 +53,7 @@ will be sent to the DHCP server at `173.252.90.132`, and requests from MAC
`fe:dc:ba:09:87:65` will be sent to the tier of servers `myGroup` (a server will
be picked according to the balancing algorithm's selection from the list of
servers returned by the `GetServersFromTier(tier string)` function of the
`DHCPServerSourcer` being used).
`DHCPServerSourcer` being used).
Overrides may be associated with an expiration timestamp in the form
"YYYY/MM/DD HH:MM TIMEZONE_OFFSET", where TIMEZONE_OFFSET is
the timezone offset with respect to UTC. `dhcplb` will convert the timestamp
@@ -115,20 +115,27 @@ At the moment this is a bit complex but we will work on ways to make it easier.
### Adding a new balancing algorithm.
Adding a new algorithm can be done by implementing something that matches
the `dhcpBalancingAlgorithm` interface:
the `DHCPBalancingAlgorithm` interface:
```go
type dhcpBalancingAlgorithm interface {
type DHCPBalancingAlgorithm interface {
selectServerFromList(list []*DHCPServer, message *DHCPMessage) (*DHCPServer, error)
selectRatioBasedDhcpServer(message *DHCPMessage) (*DHCPServer, error)
updateStableServerList(list []*DHCPServer) error
updateRCServerList(list []*DHCPServer) error
setRCRatio(ratio uint32)
Name() string
}
```
Then add it to the `algorithms` map in the `configSpec.algorithm` function, in
the `config.go` file.
Do that if you want to share the algorithm with the community.
If, however, you need to implement something that you can't share, because, for
example, it's internal and specific to your infra, you can write something that
implements the `ConfigProvider` interface, in particular the
`NewDHCPBalancingAlgorithm` function.
### Adding more configuration options.
@@ -139,6 +146,7 @@ More configuration options can be added to the config JSON file using the
type ConfigProvider interface {
NewHostSourcer(sourcerType, args string, version int) (DHCPServerSourcer, error)
ParseExtras(extras json.RawMessage) (interface{}, error)
NewDHCPBalancingAlgorithm(version int) (DHCPBalancingAlgorithm, error)
}
```
@@ -153,6 +161,9 @@ implementation.
Any struct can be returned from the `ParseExtras` function and used elsewhere in
the code via the `Extras` member of a `Config` struct.
As mentioned in the section before `NewDHCPBalancingAlgorithm` can be used
to return your own specific load balancing implementation.
### Write your own logic to source list of DHCP servers
If you want to change the way `dhcplb` sources the list of DHCP servers (for
View
@@ -22,18 +22,20 @@ import (
)
// ConfigProvider is an interface which provides methods to fetch the
// HostSourcer and parse extra configuration.
// HostSourcer, parse extra configuration and provide additional load balancing
// implementations.
type ConfigProvider interface {
NewHostSourcer(
sourcerType, args string, version int) (DHCPServerSourcer, error)
ParseExtras(extras json.RawMessage) (interface{}, error)
NewDHCPBalancingAlgorithm(version int) (DHCPBalancingAlgorithm, error)
}
// Config represents the server configuration.
type Config struct {
Version int
Addr *net.UDPAddr
Algorithm dhcpBalancingAlgorithm
Algorithm DHCPBalancingAlgorithm
ServerUpdateInterval time.Duration
PacketBufSize int
HostSourcer DHCPServerSourcer
@@ -101,7 +103,7 @@ func ParseConfig(jsonConfig, jsonOverrides []byte, version int, provider ConfigP
if len(jsonOverrides) == 0 {
overrides = make(map[string]Override)
} else {
var err error = nil
var err error
overrides, err = parseOverrides(jsonOverrides, version)
if err != nil {
glog.Errorf("Failed to load overrides: %s", err)
@@ -235,23 +237,37 @@ func (c *configSpec) sourcer(provider ConfigProvider) (DHCPServerSourcer, error)
}
}
func (c *configSpec) algorithm() (dhcpBalancingAlgorithm, error) {
// Available balancing algorithms
algorithms := map[string]dhcpBalancingAlgorithm{
"xid": new(modulo),
"rr": new(roundRobin),
func (c *configSpec) algorithm(provider ConfigProvider) (DHCPBalancingAlgorithm, error) {
// Balancing algorithms coming with the dhcplb source code
modulo := new(modulo)
rr := new(roundRobin)
algorithms := map[string]DHCPBalancingAlgorithm{
modulo.Name(): modulo,
rr.Name(): rr,
}
// load other non default algorithms from the ConfigProvider
providedAlgo, err := provider.NewDHCPBalancingAlgorithm(c.Version)
if err != nil {
glog.Fatalf("Provided load balancing implementation error: %s", err)
}
if providedAlgo != nil {
// TODO: check that the name is not used, if not then fatal.
algorithms[providedAlgo.Name()] = providedAlgo
}
lb, ok := algorithms[c.AlgorithmName]
if !ok {
supported := []string{}
for k := range algorithms {
supported = append(supported, k)
}
glog.Fatalf("Supported balancing algorithms: %v", supported)
glog.Fatalf(
"'%s' is not a supported balancing algorithm. "+
"Supported balancing algorithms are: %v",
c.AlgorithmName, supported)
return nil, fmt.Errorf(
"'%s' is not a supported balancing algorithm", c.AlgorithmName)
}
lb.setRCRatio(c.RCRatio)
lb.SetRCRatio(c.RCRatio)
return lb, nil
}
@@ -270,7 +286,7 @@ func newConfig(spec *configSpec, overrides map[string]Override, provider ConfigP
Zone: "",
}
algo, err := spec.algorithm()
algo, err := spec.algorithm(provider)
if err != nil {
return nil, err
}
@@ -325,6 +341,7 @@ type ConfigBroadcaster struct {
receivers []chan<- *Config
}
// NewConfigBroadcaster returns an instance of ConfigBroadcaster
func NewConfigBroadcaster(input <-chan *Config) *ConfigBroadcaster {
bcast := &ConfigBroadcaster{
input: input,
View
@@ -61,8 +61,7 @@ func (d *DHCPServer) disconnect() error {
defer d.connLock.Unlock()
if d.conn != nil {
glog.Infof("Closing connection to %s", d)
err := d.conn.Close()
if err != nil {
if err := d.conn.Close(); err != nil {
return err
}
d.conn = nil
@@ -72,21 +71,21 @@ func (d *DHCPServer) disconnect() error {
func (d *DHCPServer) sendTo(packet []byte) error {
if d.conn == nil {
return fmt.Errorf("No connection open to %s", d)
glog.Errorf("No connection open to %s.", d)
if err := d.connect(); err != nil {
return err
}
}
_, err := d.conn.Write(packet)
if err != nil {
if _, err := d.conn.Write(packet); err != nil {
// if failed, try to re-open socket and try again once
err = d.connect()
if err != nil {
if err := d.connect(); err != nil {
return err
}
_, err := d.conn.Write(packet)
if err != nil {
if _, err := d.conn.Write(packet); err != nil {
return err
}
}
return err
return nil
}
func (d *DHCPServer) String() string {
View
@@ -91,7 +91,7 @@ func selectDestinationServer(config *Config, message *DHCPMessage) (*DHCPServer,
return nil, err
}
if server == nil {
server, err = config.Algorithm.selectRatioBasedDhcpServer(message)
server, err = config.Algorithm.SelectRatioBasedDhcpServer(message)
}
return server, err
}
@@ -166,7 +166,7 @@ func handleTierOverride(config *Config, tier string, message *DHCPMessage) (*DHC
return nil, fmt.Errorf("Sourcer returned no servers")
}
// pick server according to the configured algorithm
server, err := config.Algorithm.selectServerFromList(servers, message)
server, err := config.Algorithm.SelectServerFromList(servers, message)
if err != nil {
return nil, fmt.Errorf("Failed to select server: %s", err)
}
View
@@ -28,12 +28,18 @@ func (m *DHCPMessage) id() id {
return id(fmt.Sprintf("%s%d%x", m.Peer.IP, m.XID, m.ClientID))
}
type dhcpBalancingAlgorithm interface {
selectServerFromList(list []*DHCPServer, message *DHCPMessage) (*DHCPServer, error)
selectRatioBasedDhcpServer(message *DHCPMessage) (*DHCPServer, error)
updateStableServerList(list []*DHCPServer) error
updateRCServerList(list []*DHCPServer) error
setRCRatio(ratio uint32)
// DHCPBalancingAlgorithm defines an interface for load balancing algorithms.
// Users can implement their own and add them to config.go (in the
// configSpec.algorithm method)
type DHCPBalancingAlgorithm interface {
SelectServerFromList(list []*DHCPServer, message *DHCPMessage) (*DHCPServer, error)
SelectRatioBasedDhcpServer(message *DHCPMessage) (*DHCPServer, error)
UpdateStableServerList(list []*DHCPServer) error
UpdateRCServerList(list []*DHCPServer) error
SetRCRatio(ratio uint32)
// An unique name for the algorithm, this string can be used in the
// configuration file, in the section where the algorithm is selecetd.
Name() string
}
// Server is the main interface implementing the DHCP server.
View
@@ -24,40 +24,44 @@ type modulo struct {
rcRatio uint32
}
func (m *modulo) Name() string {
return "xid"
}
func (m *modulo) getHash(token []byte) uint32 {
hasher := fnv.New32a()
hasher.Write(token)
hash := hasher.Sum32()
return hash
}
func (m *modulo) setRCRatio(ratio uint32) {
func (m *modulo) SetRCRatio(ratio uint32) {
atomic.StoreUint32(&m.rcRatio, ratio)
}
func (m *modulo) selectServerFromList(list []*DHCPServer, message *DHCPMessage) (*DHCPServer, error) {
func (m *modulo) SelectServerFromList(list []*DHCPServer, message *DHCPMessage) (*DHCPServer, error) {
hash := m.getHash(message.ClientID)
if len(list) == 0 {
return nil, errors.New("Server list is empty")
}
return list[hash%uint32(len(list))], nil
}
func (m *modulo) selectRatioBasedDhcpServer(message *DHCPMessage) (*DHCPServer, error) {
func (m *modulo) SelectRatioBasedDhcpServer(message *DHCPMessage) (*DHCPServer, error) {
m.lock.RLock()
defer m.lock.RUnlock()
hash := m.getHash(message.ClientID)
// convert to a number 0-100 and then see if it should be RC
if hash%100 < m.rcRatio {
return m.selectServerFromList(m.rc, message)
return m.SelectServerFromList(m.rc, message)
}
// otherwise go to stable
return m.selectServerFromList(m.stable, message)
return m.SelectServerFromList(m.stable, message)
}
func (m *modulo) updateServerList(name string, list []*DHCPServer, ptr *[]*DHCPServer) error {
func (m *modulo) UpdateServerList(name string, list []*DHCPServer, ptr *[]*DHCPServer) error {
m.lock.Lock()
defer m.lock.Unlock()
@@ -69,10 +73,10 @@ func (m *modulo) updateServerList(name string, list []*DHCPServer, ptr *[]*DHCPS
return nil
}
func (m *modulo) updateStableServerList(list []*DHCPServer) error {
return m.updateServerList("stable", list, &m.stable)
func (m *modulo) UpdateStableServerList(list []*DHCPServer) error {
return m.UpdateServerList("stable", list, &m.stable)
}
func (m *modulo) updateRCServerList(list []*DHCPServer) error {
return m.updateServerList("rc", list, &m.rc)
func (m *modulo) UpdateRCServerList(list []*DHCPServer) error {
return m.UpdateServerList("rc", list, &m.rc)
}
View
@@ -15,7 +15,7 @@ import (
func Test_Empty(t *testing.T) {
subject := new(modulo)
_, err := subject.selectRatioBasedDhcpServer(&DHCPMessage{
_, err := subject.SelectRatioBasedDhcpServer(&DHCPMessage{
ClientID: []byte{0},
})
if err == nil {
@@ -39,12 +39,12 @@ func Test_Hash(t *testing.T) {
Port: i, //use port to tell if we picked the right one
}
}
subject.updateStableServerList(servers)
subject.UpdateStableServerList(servers)
for i, v := range tests {
msg := DHCPMessage{
ClientID: v,
}
server, err := subject.selectRatioBasedDhcpServer(&msg)
server, err := subject.SelectRatioBasedDhcpServer(&msg)
if err != nil {
t.Fatalf("Unexpected error selecting server: %s", err)
}
View
@@ -180,6 +180,7 @@ func (p Packet6) Duid() ([]byte, error) {
return m.getOption(ClientID)
}
// DuidTypeName returns a string representing the type of DUID for the packet.
func (p Packet6) DuidTypeName() (string, error) {
duid, err := p.Duid()
if err != nil {
@@ -200,7 +201,7 @@ func (p Packet6) DuidTypeName() (string, error) {
}
}
// GetInnerMostPeerAddress returns the peer address in the inner most relay info
// GetInnerMostPeerAddr returns the peer address in the inner most relay info
// header, this is typically the mac address of the relay closer to the dhcp
// client making the request.
func (p Packet6) GetInnerMostPeerAddr() (net.IP, error) {
@@ -238,13 +239,12 @@ func (p Packet6) Mac() ([]byte, error) {
ip, err := p.GetInnerMostPeerAddr()
if err != nil {
return nil, err
} else {
_, mac, err := eui64.ParseIP(ip)
if err != nil {
return nil, err
}
return mac, nil
}
_, mac, err := eui64.ParseIP(ip)
if err != nil {
return nil, err
}
return mac, nil
}
// for DUIDLL[T], the last 6 bytes of the duid will be the MAC address
return duid[len(duid)-6:], nil
View
@@ -174,9 +174,9 @@ func TestDuidUUID(t *testing.T) {
0x10, 0xaa, 0xeb, 0x0a, 0x5b, 0x3f, 0xe8, 0x9d,
0x0f, 0x56}
ensure.DeepEqual(t, duid, expected)
mac, err_mac := packet.Mac()
if err_mac != nil {
t.Fatalf("Error extracting mac from peer-address relayinfo: %s", err_mac)
mac, errMac := packet.Mac()
if errMac != nil {
t.Fatalf("Error extracting mac from peer-address relayinfo: %s", errMac)
}
if FormatID(mac) != "24:8a:07:56:dc:a4" {
t.Fatalf("Expected mac %s but got %s", "24:8a:07:56:de:b0", FormatID(mac))
Oops, something went wrong.

0 comments on commit df7f949

Please sign in to comment.