Pull request: hup-reload
Merge in DNS/adguard-home from hup-reload to master Squashed commit of the following: commit 5cd4ab85bdc7544a4eded2a61f5a5571175daa44 Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Fri Oct 7 19:58:17 2022 +0300 next: imp signal hdlr commit 8fd18e749fec46982d26fc408e661bd802586c37 Merge: a8780455f1dd3334Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Fri Oct 7 19:46:48 2022 +0300 Merge branch 'master' into hup-reload commit a87804550e15d7fe3d9ded2e5a736c395f96febd Merge: 349dbe54960a7a75Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Fri Oct 7 15:49:23 2022 +0300 Merge branch 'master' into hup-reload commit 349dbe54fe27eeaf56776c73c3cc5649018d4c60 Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Fri Oct 7 15:43:52 2022 +0300 next: imp docs, names commit 7287a86d283489127453009267911003cea5227e Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Fri Oct 7 13:39:44 2022 +0300 WIP all: impl dynamic reconfiguration
This commit is contained in:
40
internal/next/configmgr/config.go
Normal file
40
internal/next/configmgr/config.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package configmgr
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/AdguardTeam/golibs/timeutil"
|
||||
)
|
||||
|
||||
// Configuration Structures
|
||||
|
||||
// config is the top-level on-disk configuration structure.
|
||||
type config struct {
|
||||
DNS *dnsConfig `yaml:"dns"`
|
||||
HTTP *httpConfig `yaml:"http"`
|
||||
// TODO(a.garipov): Use.
|
||||
SchemaVersion int `yaml:"schema_version"`
|
||||
// TODO(a.garipov): Use.
|
||||
DebugPprof bool `yaml:"debug_pprof"`
|
||||
Verbose bool `yaml:"verbose"`
|
||||
}
|
||||
|
||||
// dnsConfig is the on-disk DNS configuration.
|
||||
//
|
||||
// TODO(a.garipov): Validate.
|
||||
type dnsConfig struct {
|
||||
Addresses []netip.AddrPort `yaml:"addresses"`
|
||||
BootstrapDNS []string `yaml:"bootstrap_dns"`
|
||||
UpstreamDNS []string `yaml:"upstream_dns"`
|
||||
UpstreamTimeout timeutil.Duration `yaml:"upstream_timeout"`
|
||||
}
|
||||
|
||||
// httpConfig is the on-disk web API configuration.
|
||||
//
|
||||
// TODO(a.garipov): Validate.
|
||||
type httpConfig struct {
|
||||
Addresses []netip.AddrPort `yaml:"addresses"`
|
||||
SecureAddresses []netip.AddrPort `yaml:"secure_addresses"`
|
||||
Timeout timeutil.Duration `yaml:"timeout"`
|
||||
ForceHTTPS bool `yaml:"force_https"`
|
||||
}
|
||||
205
internal/next/configmgr/configmgr.go
Normal file
205
internal/next/configmgr/configmgr.go
Normal file
@@ -0,0 +1,205 @@
|
||||
// Package configmgr defines the AdGuard Home on-disk configuration entities and
|
||||
// configuration manager.
|
||||
package configmgr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/websvc"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Configuration Manager
|
||||
|
||||
// Manager handles full and partial changes in the configuration, persisting
|
||||
// them to disk if necessary.
|
||||
type Manager struct {
|
||||
// updMu makes sure that at most one reconfiguration is performed at a time.
|
||||
// updMu protects all fields below.
|
||||
updMu *sync.RWMutex
|
||||
|
||||
// dns is the DNS service.
|
||||
dns *dnssvc.Service
|
||||
|
||||
// Web is the Web API service.
|
||||
web *websvc.Service
|
||||
|
||||
// current is the current configuration.
|
||||
current *config
|
||||
|
||||
// fileName is the name of the configuration file.
|
||||
fileName string
|
||||
}
|
||||
|
||||
// New creates a new *Manager that persists changes to the file pointed to by
|
||||
// fileName. It reads the configuration file and populates the service fields.
|
||||
// start is the startup time of AdGuard Home.
|
||||
func New(fileName string, start time.Time) (m *Manager, err error) {
|
||||
defer func() { err = errors.Annotate(err, "reading config") }()
|
||||
|
||||
conf := &config{}
|
||||
f, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
defer func() { err = errors.WithDeferred(err, f.Close()) }()
|
||||
|
||||
err = yaml.NewDecoder(f).Decode(conf)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Move into a separate function and add other logging
|
||||
// settings.
|
||||
if conf.Verbose {
|
||||
log.SetLevel(log.DEBUG)
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Validate the configuration structure. Return an error
|
||||
// if it's incorrect.
|
||||
|
||||
m = &Manager{
|
||||
updMu: &sync.RWMutex{},
|
||||
current: conf,
|
||||
fileName: fileName,
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Get the context with the timeout from the arguments?
|
||||
const assemblyTimeout = 5 * time.Second
|
||||
ctx, cancel := context.WithTimeout(context.Background(), assemblyTimeout)
|
||||
defer cancel()
|
||||
|
||||
err = m.assemble(ctx, conf, start)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// assemble creates all services and puts them into the corresponding fields.
|
||||
// The fields of conf must not be modified after calling assemble.
|
||||
func (m *Manager) assemble(ctx context.Context, conf *config, start time.Time) (err error) {
|
||||
dnsConf := &dnssvc.Config{
|
||||
Addresses: conf.DNS.Addresses,
|
||||
BootstrapServers: conf.DNS.BootstrapDNS,
|
||||
UpstreamServers: conf.DNS.UpstreamDNS,
|
||||
UpstreamTimeout: conf.DNS.UpstreamTimeout.Duration,
|
||||
}
|
||||
err = m.updateDNS(ctx, dnsConf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("assembling dnssvc: %w", err)
|
||||
}
|
||||
|
||||
webSvcConf := &websvc.Config{
|
||||
ConfigManager: m,
|
||||
// TODO(a.garipov): Fill from config file.
|
||||
TLS: nil,
|
||||
Start: start,
|
||||
Addresses: conf.HTTP.Addresses,
|
||||
SecureAddresses: conf.HTTP.SecureAddresses,
|
||||
Timeout: conf.HTTP.Timeout.Duration,
|
||||
ForceHTTPS: conf.HTTP.ForceHTTPS,
|
||||
}
|
||||
|
||||
err = m.updateWeb(ctx, webSvcConf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("assembling websvc: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DNS returns the current DNS service. It is safe for concurrent use.
|
||||
func (m *Manager) DNS() (dns agh.ServiceWithConfig[*dnssvc.Config]) {
|
||||
m.updMu.RLock()
|
||||
defer m.updMu.RUnlock()
|
||||
|
||||
return m.dns
|
||||
}
|
||||
|
||||
// UpdateDNS implements the [websvc.ConfigManager] interface for *Manager. The
|
||||
// fields of c must not be modified after calling UpdateDNS.
|
||||
func (m *Manager) UpdateDNS(ctx context.Context, c *dnssvc.Config) (err error) {
|
||||
m.updMu.Lock()
|
||||
defer m.updMu.Unlock()
|
||||
|
||||
// TODO(a.garipov): Update and write the configuration file. Return an
|
||||
// error if something went wrong.
|
||||
|
||||
err = m.updateDNS(ctx, c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reassembling dnssvc: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateDNS recreates the DNS service. m.updMu is expected to be locked.
|
||||
func (m *Manager) updateDNS(ctx context.Context, c *dnssvc.Config) (err error) {
|
||||
if prev := m.dns; prev != nil {
|
||||
err = prev.Shutdown(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("shutting down dns svc: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
svc, err := dnssvc.New(c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating dns svc: %w", err)
|
||||
}
|
||||
|
||||
m.dns = svc
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Web returns the current web service. It is safe for concurrent use.
|
||||
func (m *Manager) Web() (web agh.ServiceWithConfig[*websvc.Config]) {
|
||||
m.updMu.RLock()
|
||||
defer m.updMu.RUnlock()
|
||||
|
||||
return m.web
|
||||
}
|
||||
|
||||
// UpdateWeb implements the [websvc.ConfigManager] interface for *Manager. The
|
||||
// fields of c must not be modified after calling UpdateWeb.
|
||||
func (m *Manager) UpdateWeb(ctx context.Context, c *websvc.Config) (err error) {
|
||||
m.updMu.Lock()
|
||||
defer m.updMu.Unlock()
|
||||
|
||||
// TODO(a.garipov): Update and write the configuration file. Return an
|
||||
// error if something went wrong.
|
||||
|
||||
err = m.updateWeb(ctx, c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reassembling websvc: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateWeb recreates the web service. m.upd is expected to be locked.
|
||||
func (m *Manager) updateWeb(ctx context.Context, c *websvc.Config) (err error) {
|
||||
if prev := m.web; prev != nil {
|
||||
err = prev.Shutdown(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("shutting down web svc: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
m.web = websvc.New(c)
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user