Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow outdated files on mirrors #188

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a84f673
selection: Move filtering in a separate Filter function, add tests
elboulangero Dec 20, 2024
eafac94
selection: Simplify Filter() a bit
elboulangero Jan 9, 2025
95a1817
selection: Nitpick with comparison
elboulangero Jan 9, 2025
2b8ad4b
Add HasAnyPrefix helper
elboulangero Dec 19, 2024
a937898
Add trivial helpers to know if a mirror is up, or to give its status
elboulangero Jan 9, 2025
030953b
monitor: Move healthCheck implementation in another function
elboulangero Dec 19, 2024
3c2d762
Add the protocol to the logs
elboulangero Jan 10, 2025
dfcd4ed
Misc trivial changes to prepare the code for relative URLs support
elboulangero Jan 10, 2025
e0ed026
Add AbsoluteURL field in Mirror
elboulangero Jan 10, 2025
10cf708
Support relative URLs in the mirror field `HttpURL`
elboulangero Jan 10, 2025
875bbee
Decouple DownReason and ExcludeReason
elboulangero Jan 10, 2025
cdb3d1f
Add support for HTTP+HTTPS mirrors
elboulangero Jan 10, 2025
c7904d2
Database upgrade to support HTTP+HTTPS mirrors
elboulangero Dec 14, 2024
4773621
Regenerate rpc.pb.go
elboulangero Jan 9, 2025
7700eb2
Add a setting to disallow HTTP to HTTPS redirects
elboulangero Dec 16, 2024
b427775
selection: Readability nitpicks in the modtime check
elboulangero Feb 10, 2025
9fa7cb2
selection: Prepare for new setting AllowOutdatedFiles
elboulangero Feb 10, 2025
d2f1a16
Add new configuration setting AllowOutdatedFiles
elboulangero Feb 10, 2025
da3a627
selection_test: Add test for FixTimezoneOffsets
elboulangero Feb 13, 2025
e91a9f9
selection_test: Add TestMain for minor improvements
elboulangero Feb 14, 2025
6faafbf
selection_test: Add helpers to factorize boilerplate
elboulangero Feb 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 76 additions & 15 deletions cli/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ func (c *cli) CmdList(args ...string) error {
}
}
if *down == true {
if mirror.Up == true || mirror.Enabled == false {
if IsUp(mirror) || mirror.Enabled == false {
continue
}
}
Expand Down Expand Up @@ -234,10 +234,8 @@ func (c *cli) CmdList(args ...string) error {
if *state == true {
if mirror.Enabled == false {
fmt.Fprintf(w, "\tdisabled")
} else if mirror.Up == true {
fmt.Fprintf(w, "\tup")
} else {
fmt.Fprintf(w, "\tdown")
fmt.Fprintf(w, "\t%s", StatusString(mirror))
}
fmt.Fprintf(w, " \t(%s)", stateSince.Format(time.RFC1123))
}
Expand All @@ -249,6 +247,55 @@ func (c *cli) CmdList(args ...string) error {
return nil
}

func IsHTTPOnly(m *rpc.Mirror) bool {
return strings.HasPrefix(m.HttpURL, "http://")
}

func IsHTTPSOnly(m *rpc.Mirror) bool {
return strings.HasPrefix(m.HttpURL, "https://")
}

func IsUp(m *rpc.Mirror) bool {
if m.HttpUp == m.HttpsUp {
return m.HttpUp
}
if IsHTTPOnly(m) {
return m.HttpUp
}
if IsHTTPSOnly(m) {
return m.HttpsUp
}
return false
}

func StatusString(m *rpc.Mirror) string {
var http string
var https string

if m.HttpUp {
http = "up"
} else {
http = "down"
}

if m.HttpsUp {
https = "up"
} else {
https = "down"
}

if m.HttpUp == m.HttpsUp {
return http
}
if IsHTTPOnly(m) {
return http
}
if IsHTTPSOnly(m) {
return https
}
return fmt.Sprintf("%s/%s", http, https)
}

func (c *cli) CmdAdd(args ...string) error {
cmd := SubCmd("add", "[OPTIONS] IDENTIFIER", "Add a new mirror")
http := cmd.String("http", "", "HTTP base URL")
Expand Down Expand Up @@ -284,14 +331,25 @@ func (c *cli) CmdAdd(args ...string) error {
os.Exit(-1)
}

if !strings.HasPrefix(*http, "http://") && !strings.HasPrefix(*http, "https://") {
*http = "http://" + *http
}

_, err := url.Parse(*http)
if err != nil {
fmt.Fprintf(os.Stderr, "Can't parse url\n")
if utils.HasAnyPrefix(*http, "http://", "https://") {
_, err := url.Parse(*http)
if err != nil {
fmt.Fprintf(os.Stderr, "Can't parse HTTP URL\n")
os.Exit(-1)
}
} else if strings.Contains(*http, "://") {
fmt.Fprintf(os.Stderr, "The HTTP URL has an invalid scheme\n")
os.Exit(-1)
} else {
// No scheme, that's a "relative URLs", and we do accept it.
// Note that the documentation of net/url mentions that parsing
// such URL is "invalid but may not necessarily return an error",
// so let's add a scheme before we parse it.
_, err := url.Parse("http://" + *http)
if err != nil {
fmt.Fprintf(os.Stderr, "Can't parse HTTP URL\n")
os.Exit(-1)
}
}

mirror := &mirrors.Mirror{
Expand Down Expand Up @@ -856,7 +914,12 @@ func (c *cli) CmdExport(args ...string) error {
urls = append(urls, m.RsyncURL)
}
if *http == true && m.HttpURL != "" {
urls = append(urls, m.HttpURL)
if utils.HasAnyPrefix(m.HttpURL, "http://", "https://") {
urls = append(urls, m.HttpURL)
} else {
urls = append(urls, "http://" + m.HttpURL)
urls = append(urls, "https://" + m.HttpURL)
}
}
if *ftp == true && m.FtpURL != "" {
urls = append(urls, m.FtpURL)
Expand Down Expand Up @@ -1014,10 +1077,8 @@ func (c *cli) CmdStats(args ...string) error {
fmt.Fprintf(w, "Identifier:\t%s\n", name)
if !reply.Mirror.Enabled {
fmt.Fprintf(w, "Status:\tdisabled\n")
} else if reply.Mirror.Up {
fmt.Fprintf(w, "Status:\tup\n")
} else {
fmt.Fprintf(w, "Status:\tdown\n")
fmt.Fprintf(w, "Status:\t%s\n", StatusString(reply.Mirror))
}
fmt.Fprintf(w, "Download requests:\t%d\n", reply.Requests)
fmt.Fprint(w, "Bytes transferred:\t")
Expand Down
16 changes: 16 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func defaultConfig() Configuration {
OutputMode: "auto",
ListenAddress: ":8080",
Gzip: false,
AllowHTTPToHTTPSRedirects: true,
SameDownloadInterval: 600,
RedisAddress: "127.0.0.1:6379",
RedisPassword: "",
Expand Down Expand Up @@ -71,6 +72,7 @@ type Configuration struct {
OutputMode string `yaml:"OutputMode"`
ListenAddress string `yaml:"ListenAddress"`
Gzip bool `yaml:"Gzip"`
AllowHTTPToHTTPSRedirects bool `yaml:"AllowHTTPToHTTPSRedirects"`
SameDownloadInterval int `yaml:"SameDownloadInterval"`
RedisAddress string `yaml:"RedisAddress"`
RedisPassword string `yaml:"RedisPassword"`
Expand All @@ -88,6 +90,7 @@ type Configuration struct {
DisallowRedirects bool `yaml:"DisallowRedirects"`
WeightDistributionRange float32 `yaml:"WeightDistributionRange"`
DisableOnMissingFile bool `yaml:"DisableOnMissingFile"`
AllowOutdatedFiles []OutdatedFilesConfig `yaml:"AllowOutdatedFiles"`
Fallbacks []fallback `yaml:"Fallbacks"`

RedisSentinelMasterName string `yaml:"RedisSentinelMasterName"`
Expand All @@ -113,6 +116,11 @@ type hashing struct {
MD5 bool `yaml:"MD5"`
}

type OutdatedFilesConfig struct {
Prefix string `yaml:"Prefix"`
Minutes int `yaml:"Minutes"`
}

// LoadConfig loads the configuration file if it has not yet been loaded
func LoadConfig() {
if config != nil {
Expand Down Expand Up @@ -167,6 +175,14 @@ func ReloadConfig() error {
if c.RepositoryScanInterval < 0 {
c.RepositoryScanInterval = 0
}
for _, rule := range c.AllowOutdatedFiles {
if len(rule.Prefix) > 0 && rule.Prefix[0] != '/' {
return fmt.Errorf("AllowOutdatedFiles.Prefix must start with '/'")
}
if rule.Minutes < 0 {
return fmt.Errorf("AllowOutdatedFiles.Minutes must be >= 0")
}
}

if config != nil &&
(c.RedisAddress != config.RedisAddress ||
Expand Down
2 changes: 1 addition & 1 deletion core/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const (
// RedisMinimumVersion contains the minimum redis version required to run the application
RedisMinimumVersion = "3.2.0"
// DBVersion represents the current DB format version
DBVersion = 1
DBVersion = 2
// DBVersionKey contains the global redis key containing the DB version format
DBVersionKey = "MIRRORBITS_DB_VERSION"
)
43 changes: 32 additions & 11 deletions daemon/monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ func (m *monitor) syncLoop() {
goto end
}

if err == nil && mir.Enabled == true && mir.Up == false {
if err == nil && mir.Enabled == true && mir.IsUp() == false {
m.healthCheckChan <- id
}

Expand All @@ -477,22 +477,43 @@ func (m *monitor) syncLoop() {

// Do an actual health check against a given mirror
func (m *monitor) healthCheck(mirror mirrors.Mirror) error {
// Format log output
format := "%-" + fmt.Sprintf("%d.%ds", m.formatLongestID+4, m.formatLongestID+4)

// Get the URL to a random file available on this mirror
file, size, err := m.getRandomFile(mirror.ID)
if err != nil {
if err == redis.ErrNil {
return errMirrorNotScanned
} else if !database.RedisIsLoading(err) {
log.Warningf(format+"Error: Cannot obtain a random file: %s", mirror.Name, err)
log.Warningf("%s: Error: Cannot obtain a random file: %s", mirror.Name, err)
}
return err
}

// Perform health check(s)
if utils.HasAnyPrefix(mirror.HttpURL, "http://", "https://") {
err = m.healthCheckDo(&mirror, mirror.HttpURL, file, size)
} else {
err = m.healthCheckDo(&mirror, "http://"+mirror.HttpURL, file, size)
err2 := m.healthCheckDo(&mirror, "https://"+mirror.HttpURL, file, size)
if err2 != nil {
err = err2
}
}

return err
}

func (m *monitor) healthCheckDo(mirror *mirrors.Mirror, url string, file string, size int64) error {
// Get protocol
proto := mirrors.HTTP
if strings.HasPrefix(url, "https://") {
proto = mirrors.HTTPS
}

// Format log output
format := "%-" + fmt.Sprintf("%d.%ds %-5s ", m.formatLongestID+4, m.formatLongestID+4, proto)

// Prepare the HTTP request
req, err := http.NewRequest("HEAD", strings.TrimRight(mirror.HttpURL, "/")+file, nil)
req, err := http.NewRequest("HEAD", strings.TrimRight(url, "/")+file, nil)
req.Header.Set("User-Agent", userAgent)
req.Close = true

Expand All @@ -506,7 +527,7 @@ func (m *monitor) healthCheck(mirror mirrors.Mirror) error {
go func() {
select {
case <-m.stop:
log.Debugf("Aborting health-check for %s", mirror.HttpURL)
log.Debugf("Aborting health-check for %s", url)
cancel()
case <-ctx.Done():
}
Expand Down Expand Up @@ -536,7 +557,7 @@ func (m *monitor) healthCheck(mirror mirrors.Mirror) error {
if strings.Contains(err.Error(), errRedirect.Error()) {
reason = "Unauthorized redirect"
}
markErr := mirrors.MarkMirrorDown(m.redis, mirror.ID, reason)
markErr := mirrors.MarkMirrorDown(m.redis, mirror.ID, proto, reason)
if markErr != nil {
log.Errorf(format+"Unable to mark mirror as down: %s", mirror.Name, markErr)
}
Expand All @@ -546,7 +567,7 @@ func (m *monitor) healthCheck(mirror mirrors.Mirror) error {

switch statusCode {
case 200:
err = mirrors.MarkMirrorUp(m.redis, mirror.ID)
err = mirrors.MarkMirrorUp(m.redis, mirror.ID, proto)
if err != nil {
log.Errorf(format+"Unable to mark mirror as up: %s", mirror.Name, err)
}
Expand All @@ -557,7 +578,7 @@ func (m *monitor) healthCheck(mirror mirrors.Mirror) error {
log.Noticef(format+"Up! (%dms)", mirror.Name, elapsed/time.Millisecond)
}
case 404:
err = mirrors.MarkMirrorDown(m.redis, mirror.ID, fmt.Sprintf("File not found %s (error 404)", file))
err = mirrors.MarkMirrorDown(m.redis, mirror.ID, proto, fmt.Sprintf("File not found %s (error 404)", file))
if err != nil {
log.Errorf(format+"Unable to mark mirror as down: %s", mirror.Name, err)
}
Expand All @@ -569,7 +590,7 @@ func (m *monitor) healthCheck(mirror mirrors.Mirror) error {
}
log.Errorf(format+"Error: File %s not found (error 404)", mirror.Name, file)
default:
err = mirrors.MarkMirrorDown(m.redis, mirror.ID, fmt.Sprintf("Got status code %d", statusCode))
err = mirrors.MarkMirrorDown(m.redis, mirror.ID, proto, fmt.Sprintf("Got status code %d", statusCode))
if err != nil {
log.Errorf(format+"Unable to mark mirror as down: %s", mirror.Name, err)
}
Expand Down
3 changes: 3 additions & 0 deletions database/upgrader/upgrader.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package upgrader
import (
"github.com/etix/mirrorbits/database/interfaces"
v1 "github.com/etix/mirrorbits/database/v1"
v2 "github.com/etix/mirrorbits/database/v2"
)

// Upgrader is an interface to implement a database upgrade strategy
Expand All @@ -18,6 +19,8 @@ func GetUpgrader(redis interfaces.Redis, version int) Upgrader {
switch version {
case 1:
return v1.NewUpgraderV1(redis)
case 2:
return v2.NewUpgraderV2(redis)
}
return nil
}
Loading
Loading