core/internal/network: move to supervisor

Test Plan: behaviour shouldn't change, covered by existing tests

Bug: T653

X-Origin-Diff: phab/D430
GitOrigin-RevId: b92f0953daba6da84bad96120cde2021c4a82e5c
diff --git a/core/internal/network/BUILD.bazel b/core/internal/network/BUILD.bazel
index 78e4885..7e45086 100644
--- a/core/internal/network/BUILD.bazel
+++ b/core/internal/network/BUILD.bazel
@@ -9,7 +9,7 @@
     importpath = "git.monogon.dev/source/nexantic.git/core/internal/network",
     visibility = ["//:__subpackages__"],
     deps = [
-        "//core/internal/common/service:go_default_library",
+        "//core/internal/common/supervisor:go_default_library",
         "@com_github_insomniacslk_dhcp//dhcpv4:go_default_library",
         "@com_github_insomniacslk_dhcp//dhcpv4/nclient4:go_default_library",
         "@com_github_vishvananda_netlink//:go_default_library",
diff --git a/core/internal/network/dhcp.go b/core/internal/network/dhcp.go
index 9a2ba8c..983c25c 100644
--- a/core/internal/network/dhcp.go
+++ b/core/internal/network/dhcp.go
@@ -21,6 +21,8 @@
 	"fmt"
 	"net"
 
+	"git.monogon.dev/source/nexantic.git/core/internal/common/supervisor"
+
 	"github.com/insomniacslk/dhcp/dhcpv4"
 	"github.com/insomniacslk/dhcp/dhcpv4/nclient4"
 	"github.com/vishvananda/netlink"
@@ -28,14 +30,12 @@
 )
 
 type dhcpClient struct {
-	reqC   chan *dhcpStatusReq
-	logger *zap.Logger
+	reqC chan *dhcpStatusReq
 }
 
-func newDHCPClient(logger *zap.Logger) *dhcpClient {
+func newDHCPClient() *dhcpClient {
 	return &dhcpClient{
-		logger: logger,
-		reqC:   make(chan *dhcpStatusReq),
+		reqC: make(chan *dhcpStatusReq),
 	}
 }
 
@@ -65,54 +65,69 @@
 	return fmt.Sprintf("Address: %s, Gateway: %s, DNS: %v", s.address.String(), s.gateway.String(), s.dns)
 }
 
-func (c *dhcpClient) run(ctx context.Context, iface netlink.Link) {
-	// Channel updated with address once one gets assigned/updated
-	newC := make(chan *dhcpStatus)
-	// Status requests waiting for configuration
-	waiters := []*dhcpStatusReq{}
+func (c *dhcpClient) run(iface netlink.Link) supervisor.Runnable {
+	return func(ctx context.Context) error {
+		logger := supervisor.Logger(ctx)
 
-	// Start lease acquisition
-	// TODO(q3k): actually maintain the lease instead of hoping we never get
-	// kicked off.
-	client, err := nclient4.New(iface.Attrs().Name)
-	if err != nil {
-		panic(err)
-	}
-	go func() {
-		_, ack, err := client.Request(ctx)
+		// Channel updated with address once one gets assigned/updated
+		newC := make(chan *dhcpStatus)
+		// Status requests waiting for configuration
+		waiters := []*dhcpStatusReq{}
+
+		// Start lease acquisition
+		// TODO(q3k): actually maintain the lease instead of hoping we never get
+		// kicked off.
+
+		client, err := nclient4.New(iface.Attrs().Name)
 		if err != nil {
-			c.logger.Error("DHCP lease request failed", zap.Error(err))
-			// TODO(q3k): implement retry logic with full state machine
+			return fmt.Errorf("nclient4.New: %w", err)
 		}
-		newC <- parseAck(ack)
-	}()
 
-	// State machine
-	// Two implicit states: WAITING -> ASSIGNED
-	// We start at WAITING, once we get a current config we move to ASSIGNED
-	// Once this becomes more complex (ie. has to handle link state changes)
-	// this should grow into a real state machine.
-	var current *dhcpStatus
-	c.logger.Info("DHCP client WAITING")
-	for {
-		select {
-		case <-ctx.Done():
-			// TODO(q3k): don't leave waiters hanging
-			return
-
-		case cfg := <-newC:
-			current = cfg
-			c.logger.Info("DHCP client ASSIGNED", zap.String("ip", current.String()))
-			for _, w := range waiters {
-				w.fulfill(current)
+		err = supervisor.Run(ctx, "client", func(ctx context.Context) error {
+			supervisor.Signal(ctx, supervisor.SignalHealthy)
+			_, ack, err := client.Request(ctx)
+			if err != nil {
+				// TODO(q3k): implement retry logic with full state machine
+				logger.Error("DHCP lease request failed", zap.Error(err))
+				return err
 			}
-			waiters = []*dhcpStatusReq{}
+			newC <- parseAck(ack)
+			supervisor.Signal(ctx, supervisor.SignalDone)
+			return nil
+		})
+		if err != nil {
+			return err
+		}
 
-		case r := <-c.reqC:
-			if current != nil || !r.wait {
-				r.fulfill(current)
-			} else {
-				waiters = append(waiters, r)
+		supervisor.Signal(ctx, supervisor.SignalHealthy)
+
+		// State machine
+		// Two implicit states: WAITING -> ASSIGNED
+		// We start at WAITING, once we get a current config we move to ASSIGNED
+		// Once this becomes more complex (ie. has to handle link state changes)
+		// this should grow into a real state machine.
+		var current *dhcpStatus
+		logger.Info("DHCP client WAITING")
+		for {
+			select {
+			case <-ctx.Done():
+				// TODO(q3k): don't leave waiters hanging
+				return err
+
+			case cfg := <-newC:
+				current = cfg
+				logger.Info("DHCP client ASSIGNED", zap.String("ip", current.String()))
+				for _, w := range waiters {
+					w.fulfill(current)
+				}
+				waiters = []*dhcpStatusReq{}
+
+			case r := <-c.reqC:
+				if current != nil || !r.wait {
+					r.fulfill(current)
+				} else {
+					waiters = append(waiters, r)
+				}
 			}
 		}
 	}
diff --git a/core/internal/network/main.go b/core/internal/network/main.go
index 01760c7..00d7fb2 100644
--- a/core/internal/network/main.go
+++ b/core/internal/network/main.go
@@ -22,7 +22,7 @@
 	"net"
 	"os"
 
-	"git.monogon.dev/source/nexantic.git/core/internal/common/service"
+	"git.monogon.dev/source/nexantic.git/core/internal/common/supervisor"
 
 	"github.com/vishvananda/netlink"
 	"go.uber.org/zap"
@@ -35,22 +35,20 @@
 )
 
 type Service struct {
-	*service.BaseService
 	config Config
+	dhcp   *dhcpClient
 
-	dhcp *dhcpClient
+	logger *zap.Logger
 }
 
 type Config struct {
 }
 
-func NewNetworkService(config Config, logger *zap.Logger) (*Service, error) {
-	s := &Service{
+func New(config Config) *Service {
+	return &Service{
 		config: config,
-		dhcp:   newDHCPClient(logger),
+		dhcp:   newDHCPClient(),
 	}
-	s.BaseService = service.NewBaseService("network", logger, s)
-	return s, nil
 }
 
 func setResolvconf(nameservers []net.IP, searchDomains []string) error {
@@ -82,7 +80,7 @@
 	}
 
 	if gw.IsUnspecified() {
-		s.Logger.Info("No default route set, only local network will be reachable", zap.String("local", addr.String()))
+		s.logger.Info("No default route set, only local network will be reachable", zap.String("local", addr.String()))
 		return nil
 	}
 
@@ -97,21 +95,24 @@
 	return nil
 }
 
-func (s *Service) useInterface(iface netlink.Link) error {
-	go s.dhcp.run(s.Context(), iface)
-
-	status, err := s.dhcp.status(s.Context(), true)
+func (s *Service) useInterface(ctx context.Context, iface netlink.Link) error {
+	err := supervisor.Run(ctx, "dhcp", s.dhcp.run(iface))
 	if err != nil {
-		return fmt.Errorf("could not get DHCP status: %v", err)
+		return err
+	}
+	status, err := s.dhcp.status(ctx, true)
+	if err != nil {
+		return fmt.Errorf("could not get DHCP status: %w", err)
 	}
 
 	if err := setResolvconf(status.dns, []string{}); err != nil {
-		s.Logger.Warn("failed to set resolvconf", zap.Error(err))
+		s.logger.Warn("failed to set resolvconf", zap.Error(err))
 	}
 
 	if err := s.addNetworkRoutes(iface, net.IPNet{IP: status.address.IP, Mask: status.address.Mask}, status.gateway); err != nil {
-		s.Logger.Warn("failed to add routes", zap.Error(err))
+		s.logger.Warn("failed to add routes", zap.Error(err))
 	}
+
 	return nil
 }
 
@@ -124,12 +125,15 @@
 	return &status.address.IP, nil
 }
 
-func (s *Service) OnStart() error {
-	s.Logger.Info("Starting network service")
+func (s *Service) Run(ctx context.Context) error {
+	s.logger = supervisor.Logger(ctx)
+	s.logger.Info("Starting network service")
+
 	links, err := netlink.LinkList()
 	if err != nil {
-		s.Logger.Fatal("Failed to list network links", zap.Error(err))
+		s.logger.Fatal("Failed to list network links", zap.Error(err))
 	}
+
 	var ethernetLinks []netlink.Link
 	for _, link := range links {
 		attrs := link.Attrs()
@@ -140,25 +144,24 @@
 				}
 				ethernetLinks = append(ethernetLinks, link)
 			} else {
-				s.Logger.Info("Ignoring non-Ethernet interface", zap.String("interface", attrs.Name))
+				s.logger.Info("Ignoring non-Ethernet interface", zap.String("interface", attrs.Name))
 			}
 		} else if link.Attrs().Name == "lo" {
 			if err := netlink.LinkSetUp(link); err != nil {
-				s.Logger.Error("Failed to take up loopback interface", zap.Error(err))
+				s.logger.Error("Failed to take up loopback interface", zap.Error(err))
 			}
 		}
 	}
 	if len(ethernetLinks) != 1 {
-		s.Logger.Warn("Network service needs exactly one link, bailing")
-		return nil
+		s.logger.Warn("Network service needs exactly one link, bailing")
+	} else {
+		link := ethernetLinks[0]
+		if err := s.useInterface(ctx, link); err != nil {
+			return fmt.Errorf("failed to bring up link %s: %w", link.Attrs().Name, err)
+		}
 	}
 
-	link := ethernetLinks[0]
-	go s.useInterface(link)
-
-	return nil
-}
-
-func (s *Service) OnStop() error {
+	supervisor.Signal(ctx, supervisor.SignalHealthy)
+	supervisor.Signal(ctx, supervisor.SignalDone)
 	return nil
 }