func GetMirrorMapUrl(mirrors Mirrors, clientInfo network.GeoIPRecord) string { var buffer bytes.Buffer buffer.WriteString("//maps.googleapis.com/maps/api/staticmap?size=520x320&sensor=false&visual_refresh=true") if clientInfo.IsValid() { buffer.WriteString(fmt.Sprintf("&markers=size:mid|color:red|%f,%f", clientInfo.Latitude, clientInfo.Longitude)) } count := 1 for i, mirror := range mirrors { if count > 9 { break } if i == 0 && clientInfo.IsValid() { // Draw a path between the client and the mirror buffer.WriteString(fmt.Sprintf("&path=color:0x17ea0bdd|weight:5|%f,%f|%f,%f", clientInfo.Latitude, clientInfo.Longitude, mirror.Latitude, mirror.Longitude)) } color := "blue" if mirror.Weight > 0 { color = "green" } buffer.WriteString(fmt.Sprintf("&markers=color:%s|label:%d|%f,%f", color, count, mirror.Latitude, mirror.Longitude)) count++ } return buffer.String() }
func TestByRank_Less(t *testing.T) { rand.Seed(time.Now().UnixNano()) /* */ c := network.GeoIPRecord{} if c.IsValid() { t.Fatalf("GeoIPRecord is supposed to be invalid") } /* */ // Generate two identical slices m1 := generateSimpleMirrorList(50) m2 := generateSimpleMirrorList(50) // Mirrors are indentical (besides name) so ByRank is expected // to randomize their order. sort.Sort(ByRank{m1, c}) differences := 0 for i, m := range m1 { if m.ID != m2[i].ID { differences++ } } if differences == 0 { t.Fatalf("Result is supposed to be randomized") } else if differences < 10 { t.Fatalf("Too many similarities, something's wrong?") } // Sort again, just to be sure the result is different m3 := generateSimpleMirrorList(50) sort.Sort(ByRank{m3, c}) differences = 0 for i, m := range m3 { if m.ID != m1[i].ID { differences++ } } if differences == 0 { t.Fatalf("Result is supposed to be different from previous run") } else if differences < 10 { t.Fatalf("Too many similarities, something's wrong?") } /* */ c = network.GeoIPRecord{ GeoIPRecord: &geoip.GeoIPRecord{ CountryCode: "FR", ContinentCode: "EU", }, ASNum: 4444, } if !c.IsValid() { t.Fatalf("GeoIPRecord is supposed to be valid") } /* asnum */ m := Mirrors{ Mirror{ ID: "M0", Asnum: 6666, }, Mirror{ ID: "M1", Asnum: 5555, }, Mirror{ ID: "M2", Asnum: 4444, }, Mirror{ ID: "M3", Asnum: 6666, }, } sort.Sort(ByRank{m, c}) if !matchingMirrorOrder(m, []string{"M2", "M0", "M1", "M3"}) { t.Fatalf("Order doesn't seem right: %s, expected M2, M0, M1, M3", formatMirrorOrder(m)) } /* distance */ m = Mirrors{ Mirror{ ID: "M0", Distance: 1000.0, }, Mirror{ ID: "M1", Distance: 999.0, }, Mirror{ ID: "M2", Distance: 1000.0, }, Mirror{ ID: "M3", Distance: 888.0, }, } sort.Sort(ByRank{m, c}) if !matchingMirrorOrder(m, []string{"M3", "M1", "M0", "M2"}) { t.Fatalf("Order doesn't seem right: %s, expected M3, M1, M0, M2", formatMirrorOrder(m)) } /* countrycode */ m = Mirrors{ Mirror{ ID: "M0", CountryFields: []string{"IT", "UK"}, }, Mirror{ ID: "M1", CountryFields: []string{"IT", "UK"}, }, Mirror{ ID: "M2", CountryFields: []string{"IT", "FR"}, }, Mirror{ ID: "M3", CountryFields: []string{"FR", "UK"}, }, } sort.Sort(ByRank{m, c}) if !matchingMirrorOrder(m, []string{"M2", "M3", "M0", "M1"}) { t.Fatalf("Order doesn't seem right: %s, expected M2, M3, M0, M1", formatMirrorOrder(m)) } /* continentcode */ c = network.GeoIPRecord{ GeoIPRecord: &geoip.GeoIPRecord{ ContinentCode: "EU", }, ASNum: 4444, } m = Mirrors{ Mirror{ ID: "M0", ContinentCode: "NA", }, Mirror{ ID: "M1", ContinentCode: "NA", }, Mirror{ ID: "M2", ContinentCode: "EU", }, Mirror{ ID: "M3", ContinentCode: "NA", }, } sort.Sort(ByRank{m, c}) if !matchingMirrorOrder(m, []string{"M2", "M0", "M1", "M3"}) { t.Fatalf("Order doesn't seem right: %s, expected M2, M0, M1, M3", formatMirrorOrder(m)) } /* */ c = network.GeoIPRecord{ GeoIPRecord: &geoip.GeoIPRecord{ CountryCode: "FR", ContinentCode: "EU", }, ASNum: 4444, } m = Mirrors{ Mirror{ ID: "M0", Distance: 100.0, CountryFields: []string{"IT", "FR"}, ContinentCode: "EU", }, Mirror{ ID: "M1", Distance: 200.0, CountryFields: []string{"FR", "CH"}, ContinentCode: "EU", }, Mirror{ ID: "M2", Distance: 1000.0, CountryFields: []string{"UK", "DE"}, Asnum: 4444, }, } sort.Sort(ByRank{m, c}) if !matchingMirrorOrder(m, []string{"M2", "M0", "M1"}) { t.Fatalf("Order doesn't seem right: %s, expected M2, M0, M1", formatMirrorOrder(m)) } }
func (h DefaultEngine) Selection(ctx *Context, cache *mirrors.Cache, fileInfo *filesystem.FileInfo, clientInfo network.GeoIPRecord) (mlist mirrors.Mirrors, excluded mirrors.Mirrors, err error) { // Get details about the requested file *fileInfo, err = cache.GetFileInfo(fileInfo.Path) if err != nil { return } // Prepare and return the list of all potential mirrors mlist, err = cache.GetMirrors(fileInfo.Path, clientInfo) if err != nil { return } // Filter safeIndex := 0 excluded = make([]mirrors.Mirror, 0, len(mlist)) var closestMirror float32 var farthestMirror float32 for i, m := range mlist { // Does it support http? Is it well formated? if !strings.HasPrefix(m.HttpURL, "http://") && !strings.HasPrefix(m.HttpURL, "https://") { m.ExcludeReason = "Invalid URL" goto delete } // Is it enabled? if !m.Enabled { m.ExcludeReason = "Disabled" goto delete } // Is it up? if !m.Up { if m.ExcludeReason == "" { m.ExcludeReason = "Down" } goto delete } // Is it the same size as source? if m.FileInfo != nil { if m.FileInfo.Size != fileInfo.Size { m.ExcludeReason = "File size mismatch" goto delete } } // Is it configured to serve its continent only? if m.ContinentOnly { if !clientInfo.IsValid() || clientInfo.ContinentCode != m.ContinentCode { m.ExcludeReason = "Continent only" goto delete } } // Is it configured to serve its country only? if m.CountryOnly { if !clientInfo.IsValid() || !utils.IsInSlice(clientInfo.CountryCode, m.CountryFields) { m.ExcludeReason = "Country only" goto delete } } // Is it in the same AS number? if m.ASOnly { if !clientInfo.IsValid() || clientInfo.ASNum != m.Asnum { m.ExcludeReason = "AS only" goto delete } } if safeIndex == 0 { closestMirror = m.Distance } else if closestMirror > m.Distance { closestMirror = m.Distance } if m.Distance > farthestMirror { farthestMirror = m.Distance } mlist[safeIndex] = mlist[i] safeIndex++ continue delete: excluded = append(excluded, m) } // Reduce the slice to its new size mlist = mlist[:safeIndex] if !clientInfo.IsValid() { // Shuffle the list //XXX Should we use the fallbacks instead? for i := range mlist { j := rand.Intn(i + 1) mlist[i], mlist[j] = mlist[j], mlist[i] } // Shortcut if !ctx.IsMirrorlist() { // Reduce the number of mirrors to process mlist = mlist[:utils.Min(5, len(mlist))] } return } // We're not interested in divisions by zero if closestMirror == 0 { closestMirror = math.SmallestNonzeroFloat32 } /* Weight distribution for random selection [Probabilistic weight] */ // Compute score for each mirror and return the mirrors eligible for weight distribution. // This includes: // - mirrors found in a 1.5x (configurable) range from the closest mirror // - mirrors targeting the given country (as primary or secondary) // - mirrors being in the same AS number totalScore := 0 baseScore := int(farthestMirror) weights := map[string]int{} for i := 0; i < len(mlist); i++ { m := &mlist[i] m.ComputedScore = baseScore - int(m.Distance) + 1 if m.Distance <= closestMirror*GetConfig().WeightDistributionRange { score := (float32(baseScore) - m.Distance) if !utils.IsPrimaryCountry(clientInfo, m.CountryFields) { score /= 2 } m.ComputedScore += int(score) } else if utils.IsPrimaryCountry(clientInfo, m.CountryFields) { m.ComputedScore += int(float32(baseScore) - (m.Distance * 5)) } else if utils.IsAdditionalCountry(clientInfo, m.CountryFields) { m.ComputedScore += int(float32(baseScore) - closestMirror) } if m.Asnum == clientInfo.ASNum { m.ComputedScore += baseScore / 2 } floatingScore := float64(m.ComputedScore) + (float64(m.ComputedScore) * (float64(m.Score) / 100)) + 0.5 // The minimum allowed score is 1 m.ComputedScore = int(math.Max(floatingScore, 1)) if m.ComputedScore > baseScore { // The weight must always be > 0 to not break the randomization below totalScore += m.ComputedScore - baseScore weights[m.ID] = m.ComputedScore - baseScore } } // Get the final number of mirrors selected for weight distribution selected := len(weights) // Sort mirrors by computed score sort.Sort(mirrors.ByComputedScore{mlist}) if selected > 1 { if ctx.IsMirrorlist() { // Don't reorder the results, just set the percentage for i := 0; i < selected; i++ { id := mlist[i].ID for j := 0; j < len(mlist); j++ { if mlist[j].ID == id { mlist[j].Weight = float32(float64(weights[id]) * 100 / float64(totalScore)) break } } } } else { // Randomize the order of the selected mirrors considering their weights weightedMirrors := make([]mirrors.Mirror, selected) rest := totalScore for i := 0; i < selected; i++ { var id string rv := rand.Int31n(int32(rest)) s := 0 for k, v := range weights { s += v if int32(s) > rv { id = k break } } for _, m := range mlist { if m.ID == id { m.Weight = float32(float64(weights[id]) * 100 / float64(totalScore)) weightedMirrors[i] = m break } } rest -= weights[id] delete(weights, id) } // Replace the head of the list by its reordered counterpart mlist = append(weightedMirrors, mlist[selected:]...) // Reduce the number of mirrors to return v := math.Min(math.Min(5, float64(selected)), float64(len(mlist))) mlist = mlist[:int(v)] } } else if selected == 1 && len(mlist) > 0 { mlist[0].Weight = 100 } return }