mirror of
https://github.com/safing/portbase
synced 2025-04-11 04:59:09 +00:00
253 lines
6.6 KiB
Go
253 lines
6.6 KiB
Go
package modules
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/tevino/abool"
|
|
)
|
|
|
|
// Module Status Values.
|
|
const (
|
|
StatusDead uint8 = 0 // not prepared, not started
|
|
StatusPreparing uint8 = 1
|
|
StatusOffline uint8 = 2 // prepared, not started
|
|
StatusStopping uint8 = 3
|
|
StatusStarting uint8 = 4
|
|
StatusOnline uint8 = 5 // online and running
|
|
)
|
|
|
|
// Module Failure Status Values.
|
|
const (
|
|
FailureNone uint8 = 0
|
|
FailureHint uint8 = 1
|
|
FailureWarning uint8 = 2
|
|
FailureError uint8 = 3
|
|
)
|
|
|
|
// Ready Stati.
|
|
const (
|
|
statusWaiting uint8 = iota
|
|
statusReady
|
|
statusNothingToDo
|
|
)
|
|
|
|
var (
|
|
failureUpdateNotifyFunc func(moduleFailure uint8, id, title, msg string)
|
|
failureUpdateNotifyFuncEnabled = abool.NewBool(false)
|
|
failureUpdateNotifyFuncReady = abool.NewBool(false)
|
|
)
|
|
|
|
// SetFailureUpdateNotifyFunc sets a function that is called on every change
|
|
// of a module's failure status.
|
|
func SetFailureUpdateNotifyFunc(fn func(moduleFailure uint8, id, title, msg string)) bool {
|
|
if failureUpdateNotifyFuncEnabled.SetToIf(false, true) {
|
|
failureUpdateNotifyFunc = fn
|
|
failureUpdateNotifyFuncReady.Set()
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Online returns whether the module is online.
|
|
func (m *Module) Online() bool {
|
|
return m.Status() == StatusOnline
|
|
}
|
|
|
|
// OnlineSoon returns whether the module is or is about to be online.
|
|
func (m *Module) OnlineSoon() bool {
|
|
if moduleMgmtEnabled.IsSet() &&
|
|
!m.enabled.IsSet() &&
|
|
!m.enabledAsDependency.IsSet() {
|
|
return false
|
|
}
|
|
return !m.stopFlag.IsSet()
|
|
}
|
|
|
|
// Status returns the current module status.
|
|
func (m *Module) Status() uint8 {
|
|
m.RLock()
|
|
defer m.RUnlock()
|
|
|
|
return m.status
|
|
}
|
|
|
|
// FailureStatus returns the current failure status, ID and message.
|
|
func (m *Module) FailureStatus() (failureStatus uint8, failureID, failureMsg string) {
|
|
m.RLock()
|
|
defer m.RUnlock()
|
|
|
|
return m.failureStatus, m.failureID, m.failureMsg
|
|
}
|
|
|
|
// Hint sets failure status to hint. This is a somewhat special failure status,
|
|
// as the module is believed to be working correctly, but there is an important
|
|
// module specific information to convey. The supplied failureID is for
|
|
// improved automatic handling within connected systems, the failureMsg is for
|
|
// humans.
|
|
// The given ID must be unique for the given title and message. A call to
|
|
// Hint(), Warning() or Error() with the same ID as the existing one will be
|
|
// ignored.
|
|
func (m *Module) Hint(id, title, msg string) {
|
|
m.setFailure(FailureHint, id, title, msg, true)
|
|
}
|
|
|
|
// Warning sets failure status to warning. The supplied failureID is for
|
|
// improved automatic handling within connected systems, the failureMsg is for
|
|
// humans.
|
|
// The given ID must be unique for the given title and message. A call to
|
|
// Hint(), Warning() or Error() with the same ID as the existing one will be
|
|
// ignored.
|
|
func (m *Module) Warning(id, title, msg string) {
|
|
m.setFailure(FailureWarning, id, title, msg, true)
|
|
}
|
|
|
|
// Error sets failure status to error. The supplied failureID is for improved
|
|
// automatic handling within connected systems, the failureMsg is for humans.
|
|
// The given ID must be unique for the given title and message. A call to
|
|
// Hint(), Warning() or Error() with the same ID as the existing one will be
|
|
// ignored.
|
|
func (m *Module) Error(id, title, msg string) {
|
|
m.setFailure(FailureError, id, title, msg, true)
|
|
}
|
|
|
|
func (m *Module) setFailure(status uint8, id, title, msg string, lockModule bool) {
|
|
var resolveFailureID string
|
|
func() {
|
|
if lockModule {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
}
|
|
|
|
// Ignore calls with the same ID.
|
|
if id == m.failureID {
|
|
return
|
|
}
|
|
|
|
// Copy data for failure status update worker.
|
|
resolveFailureID = m.failureID
|
|
|
|
// Set new failure status.
|
|
m.failureStatus = status
|
|
m.failureID = id
|
|
m.failureTitle = title
|
|
m.failureMsg = msg
|
|
}()
|
|
|
|
// Notify of module change.
|
|
m.notifyOfChange()
|
|
|
|
// Propagate failure status.
|
|
if failureUpdateNotifyFuncReady.IsSet() {
|
|
_ = m.RunWorker("failure status updater", func(context.Context) error {
|
|
// Only use data in worker that won't change anymore.
|
|
|
|
// Resolve previous failure state if available.
|
|
if resolveFailureID != "" {
|
|
failureUpdateNotifyFunc(FailureNone, resolveFailureID, "", "")
|
|
}
|
|
|
|
// Notify of new failure state.
|
|
failureUpdateNotifyFunc(status, id, title, msg)
|
|
|
|
return nil
|
|
})
|
|
}
|
|
}
|
|
|
|
// Resolve removes the failure state from the module if the given failureID matches the current failure ID. If the given failureID is an empty string, Resolve removes any failure state.
|
|
func (m *Module) Resolve(failureID string) {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
// Check if resolving is necessary.
|
|
if failureID != "" && failureID != m.failureID {
|
|
// Return immediately if not resolving any (`""`) or if the failure ID
|
|
// does not match.
|
|
return
|
|
}
|
|
|
|
// Copy data for failure status update worker.
|
|
resolveFailureID := m.failureID
|
|
|
|
// Set failure status on module.
|
|
m.failureStatus = FailureNone
|
|
m.failureID = ""
|
|
m.failureTitle = ""
|
|
m.failureMsg = ""
|
|
|
|
// Notify of module change.
|
|
m.notifyOfChange()
|
|
|
|
// Propagate failure status.
|
|
if failureUpdateNotifyFuncReady.IsSet() {
|
|
m.StartWorker("failure status updater", func(context.Context) error {
|
|
// Only use data in worker that won't change anymore.
|
|
failureUpdateNotifyFunc(FailureNone, resolveFailureID, "", "")
|
|
return nil
|
|
})
|
|
}
|
|
}
|
|
|
|
// readyToPrep returns whether all dependencies are ready for this module to prep.
|
|
func (m *Module) readyToPrep() uint8 {
|
|
// check if valid state for prepping
|
|
if m.Status() != StatusDead {
|
|
return statusNothingToDo
|
|
}
|
|
|
|
for _, dep := range m.depModules {
|
|
if dep.Status() < StatusOffline {
|
|
return statusWaiting
|
|
}
|
|
}
|
|
|
|
return statusReady
|
|
}
|
|
|
|
// readyToStart returns whether all dependencies are ready for this module to start.
|
|
func (m *Module) readyToStart() uint8 {
|
|
// check if start is wanted
|
|
if moduleMgmtEnabled.IsSet() {
|
|
if !m.enabled.IsSet() && !m.enabledAsDependency.IsSet() {
|
|
return statusNothingToDo
|
|
}
|
|
}
|
|
|
|
// check if valid state for starting
|
|
if m.Status() != StatusOffline {
|
|
return statusNothingToDo
|
|
}
|
|
|
|
// check if all dependencies are ready
|
|
for _, dep := range m.depModules {
|
|
if dep.Status() < StatusOnline {
|
|
return statusWaiting
|
|
}
|
|
}
|
|
|
|
return statusReady
|
|
}
|
|
|
|
// readyToStop returns whether all dependencies are ready for this module to stop.
|
|
func (m *Module) readyToStop() uint8 {
|
|
// check if stop is wanted
|
|
if moduleMgmtEnabled.IsSet() && !shutdownFlag.IsSet() {
|
|
if m.enabled.IsSet() || m.enabledAsDependency.IsSet() {
|
|
return statusNothingToDo
|
|
}
|
|
}
|
|
|
|
// check if valid state for stopping
|
|
if m.Status() != StatusOnline {
|
|
return statusNothingToDo
|
|
}
|
|
|
|
for _, revDep := range m.depReverse {
|
|
// not ready if a reverse dependency was started, but not yet stopped
|
|
if revDep.Status() > StatusOffline {
|
|
return statusWaiting
|
|
}
|
|
}
|
|
|
|
return statusReady
|
|
}
|