147 lines
3.2 KiB
Go
147 lines
3.2 KiB
Go
package dhcpbackend
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
dhcpapi "git.g3e.fr/syonad/two/internal/api/dhcp"
|
|
dhcpclient "git.g3e.fr/syonad/two/internal/client/dhcp"
|
|
"git.g3e.fr/syonad/two/pkg/db/statefile"
|
|
"git.g3e.fr/syonad/two/pkg/systemd"
|
|
)
|
|
|
|
const (
|
|
readyTimeout = 5 * time.Second
|
|
readyPoll = 50 * time.Millisecond
|
|
)
|
|
|
|
type Two struct {
|
|
RunDir string
|
|
}
|
|
|
|
func (b Two) runDir() string {
|
|
if b.RunDir == "" {
|
|
return dhcpapi.DefaultRunDir
|
|
}
|
|
return b.RunDir
|
|
}
|
|
|
|
func (b Two) Unit(s Subnet) string {
|
|
return dhcpapi.Unit(s.Instance())
|
|
}
|
|
|
|
func (b Two) client(s Subnet) *dhcpclient.Client {
|
|
return dhcpclient.New(dhcpapi.SocketPath(b.runDir(), s.Instance()))
|
|
}
|
|
|
|
func (b Two) statePath(s Subnet) string {
|
|
return dhcpapi.StatePath(b.runDir(), s.Instance())
|
|
}
|
|
|
|
func (b Two) waitReady(s Subnet, timeout, poll time.Duration) error {
|
|
client := b.client(s)
|
|
deadline := time.Now().Add(timeout)
|
|
|
|
var err error
|
|
for {
|
|
if _, _, err = client.GetState(); err == nil {
|
|
return nil
|
|
}
|
|
if time.Now().After(deadline) {
|
|
return fmt.Errorf("dhcp server for %s did not answer within %s: %w", s.Instance(), timeout, err)
|
|
}
|
|
time.Sleep(poll)
|
|
}
|
|
}
|
|
|
|
func (b Two) ConfigureSubnet(s Subnet) error {
|
|
if err := statefile.Remove(b.statePath(s)); err != nil {
|
|
return fmt.Errorf("remove residual state: %w", err)
|
|
}
|
|
|
|
svc, err := systemd.New()
|
|
if err != nil {
|
|
return fmt.Errorf("connect to systemd: %w", err)
|
|
}
|
|
defer svc.Close()
|
|
|
|
if err := svc.Start(b.Unit(s)); err != nil {
|
|
return fmt.Errorf("start dhcp: %w", err)
|
|
}
|
|
if err := b.waitReady(s, readyTimeout, readyPoll); err != nil {
|
|
return err
|
|
}
|
|
|
|
return b.pushSubnet(s)
|
|
}
|
|
|
|
func (b Two) pushSubnet(s Subnet) error {
|
|
subnet := dhcpapi.Subnet{
|
|
Network: s.Network.String(),
|
|
InterfaceIP: s.InterfaceIP.String(),
|
|
}
|
|
if s.VPCRoute != nil {
|
|
subnet.VPCRoute = s.VPCRoute.String()
|
|
}
|
|
if s.DefaultGateway != nil {
|
|
subnet.DefaultGateway = s.DefaultGateway.String()
|
|
}
|
|
|
|
if err := b.client(s).SetSubnet(subnet); err != nil {
|
|
return fmt.Errorf("configure dhcp for %s: %w", s.Instance(), err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (b Two) TeardownSubnet(s Subnet) error {
|
|
svc, err := systemd.New()
|
|
if err != nil {
|
|
return fmt.Errorf("connect to systemd: %w", err)
|
|
}
|
|
defer svc.Close()
|
|
|
|
unit := b.Unit(s)
|
|
if status, err := svc.Status(unit); err == nil && status.ActiveState == "active" {
|
|
if err := svc.Stop(unit); err != nil {
|
|
return fmt.Errorf("stop dhcp: %w", err)
|
|
}
|
|
}
|
|
|
|
return statefile.Remove(b.statePath(s))
|
|
}
|
|
|
|
func (b Two) SetVM(s Subnet, vmName string, res []Reservation) error {
|
|
client := b.client(s)
|
|
|
|
for _, r := range res {
|
|
host := dhcpapi.Host{
|
|
MAC: r.MAC,
|
|
IP: r.IP,
|
|
VM: vmName,
|
|
DefaultRoute: r.DefaultRoute,
|
|
}
|
|
if err := client.SetHost(host); err != nil {
|
|
return fmt.Errorf("reserve %s for vm %s on %s: %w", r.MAC, vmName, s.Instance(), err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (b Two) DelVM(s Subnet, vmName string, res []Reservation) error {
|
|
client := b.client(s)
|
|
|
|
for _, r := range res {
|
|
if err := client.DelHost(r.MAC); err != nil {
|
|
return fmt.Errorf("release %s of vm %s on %s: %w", r.MAC, vmName, s.Instance(), err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (b Two) StatePath(s Subnet) string {
|
|
return b.statePath(s)
|
|
}
|
|
|
|
func (b Two) State(s Subnet) (dhcpapi.State, string, error) {
|
|
return b.client(s).GetState()
|
|
}
|