[tor-commits] [collector/release] Archive bridge pool assignments again.

karsten at torproject.org karsten at torproject.org
Thu Sep 19 09:31:54 UTC 2019


commit b2b3363232fd1a425a6e83bde89b2687b56abc0a
Author: Karsten Loesing <karsten.loesing at gmx.net>
Date:   Fri Aug 30 11:09:23 2019 +0200

    Archive bridge pool assignments again.
    
    Implements #31558.
---
 CHANGELOG.md                                       |   1 +
 .../org/torproject/metrics/collector/Main.java     |   3 +
 .../BridgePoolAssignmentsProcessor.java            | 363 +++++++++++++++++++++
 .../metrics/collector/conf/Annotation.java         |   1 +
 .../metrics/collector/conf/Configuration.java      |   1 +
 .../org/torproject/metrics/collector/conf/Key.java |   6 +
 .../persist/BridgePoolAssignmentPersistence.java   |  34 ++
 .../collector/persist/DescriptorPersistence.java   |   2 +
 .../metrics/collector/sync/SyncPersistence.java    |   6 +
 src/main/resources/collector.properties            |  18 +
 .../metrics/collector/conf/ConfigurationTest.java  |   2 +-
 .../metrics/collector/cron/CollecTorMainTest.java  |   1 +
 12 files changed, 437 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index b38b124..4ecfb35 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@
    - Remove Cobertura from the build process.
    - Archive snowflake statistics.
    - Update to metrics-lib 2.7.0.
+   - Archive bridge pool assignments again.
 
 
 # Changes in version 1.9.1 - 2019-05-29
diff --git a/src/main/java/org/torproject/metrics/collector/Main.java b/src/main/java/org/torproject/metrics/collector/Main.java
index 6907e93..3150ffc 100644
--- a/src/main/java/org/torproject/metrics/collector/Main.java
+++ b/src/main/java/org/torproject/metrics/collector/Main.java
@@ -4,6 +4,7 @@
 package org.torproject.metrics.collector;
 
 import org.torproject.metrics.collector.bridgedescs.SanitizedBridgesWriter;
+import org.torproject.metrics.collector.bridgepools.BridgePoolAssignmentsProcessor;
 import org.torproject.metrics.collector.conf.Configuration;
 import org.torproject.metrics.collector.conf.ConfigurationException;
 import org.torproject.metrics.collector.conf.Key;
@@ -49,6 +50,8 @@ public class Main {
 
   static { // add a new main class here
     collecTorMains.put(Key.BridgedescsActivated, SanitizedBridgesWriter.class);
+    collecTorMains.put(Key.BridgePoolAssignmentsActivated,
+        BridgePoolAssignmentsProcessor.class);
     collecTorMains.put(Key.ExitlistsActivated, ExitListDownloader.class);
     collecTorMains.put(Key.UpdateindexActivated, CreateIndexJson.class);
     collecTorMains.put(Key.RelaydescsActivated, ArchiveWriter.class);
diff --git a/src/main/java/org/torproject/metrics/collector/bridgepools/BridgePoolAssignmentsProcessor.java b/src/main/java/org/torproject/metrics/collector/bridgepools/BridgePoolAssignmentsProcessor.java
new file mode 100644
index 0000000..cad91ef
--- /dev/null
+++ b/src/main/java/org/torproject/metrics/collector/bridgepools/BridgePoolAssignmentsProcessor.java
@@ -0,0 +1,363 @@
+/* Copyright 2011--2019 The Tor Project
+ * See LICENSE for licensing information */
+
+package org.torproject.metrics.collector.bridgepools;
+
+import org.torproject.metrics.collector.conf.Configuration;
+import org.torproject.metrics.collector.conf.ConfigurationException;
+import org.torproject.metrics.collector.conf.Key;
+import org.torproject.metrics.collector.cron.CollecTorMain;
+
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.time.DateTimeException;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.Stack;
+import java.util.TreeMap;
+
+public class BridgePoolAssignmentsProcessor extends CollecTorMain {
+
+  /**
+   * Class logger.
+   */
+  private static final Logger logger = LoggerFactory.getLogger(
+      BridgePoolAssignmentsProcessor.class);
+
+  /**
+   * Directory containing original, not-yet-sanitized bridge pool assignment
+   * files.
+   */
+  private File assignmentsDirectory;
+
+  /**
+   * Directory containing sanitized bridge pool assignments for tarballs.
+   */
+  private String outputPathName;
+
+  /**
+   * Directory containing recently stored sanitized bridge pool assignments.
+   */
+  private String recentPathName;
+
+  /**
+   * Timestamp format in bridge-pool-assignments line.
+   */
+  private DateTimeFormatter assignmentFormat = DateTimeFormatter.ofPattern(
+      "uuuu-MM-dd HH:mm:ss");
+
+  /**
+   * File name format.
+   */
+  private DateTimeFormatter filenameFormat = DateTimeFormatter.ofPattern(
+      "uuuu/MM/dd/uuuu-MM-dd-HH-mm-ss");
+
+  /**
+   * Initialize this class with the given configuration.
+   */
+  public BridgePoolAssignmentsProcessor(Configuration config) {
+    super(config);
+  }
+
+  /**
+   * Return the module identifier.
+   *
+   * @return Module identifier.
+   */
+  @Override
+  public String module() {
+    return "BridgePoolAssignments";
+  }
+
+  /**
+   * Return the synchronization marker.
+   *
+   * @return Synchronization marker.
+   */
+  @Override
+  protected String syncMarker() {
+    return "BridgePoolAssignments";
+  }
+
+  /**
+   * Start processing files, which includes reading original, not-yet-sanitized
+   * bridge pool assignment files from disk, splitting them into bridge pool
+   * assignment descriptors, sanitizing contained fingerprints, and writing
+   * sanitized bridge pool assignments to disk.
+   *
+   * @throws ConfigurationException Thrown if configuration values cannot be
+   *     obtained.
+   */
+  @Override
+  protected void startProcessing() throws ConfigurationException {
+    logger.info("Starting bridge-pool-assignments module of CollecTor.");
+    this.initializeConfiguration();
+    List<File> assignmentFiles = this.listAssignmentFiles();
+    for (File assignmentFile : assignmentFiles) {
+      logger.info("Processing bridge pool assignment file '{}'...",
+          assignmentFile.getAbsolutePath());
+      for (Map.Entry<LocalDateTime, SortedMap<String, String>> e
+           : this.readBridgePoolAssignments(assignmentFile).entrySet()) {
+        LocalDateTime published = e.getKey();
+        SortedMap<String, String> originalAssignments = e.getValue();
+        SortedMap<String, String> sanitizedAssignments
+            = this.sanitizeAssignments(originalAssignments);
+        if (null == sanitizedAssignments) {
+          logger.warn("Unable to sanitize assignments published at {}. "
+              + "Skipping.", published);
+          continue;
+        }
+        String formattedSanitizedAssignments = this.formatSanitizedAssignments(
+            published, sanitizedAssignments);
+        File tarballFile = Paths.get(this.outputPathName,
+            published.format(this.filenameFormat)).toFile();
+        File rsyncFile = new File(this.recentPathName,
+            tarballFile.getName());
+        File[] outputFiles = new File[] { tarballFile, rsyncFile };
+        for (File outputFile : outputFiles) {
+          if (!outputFile.exists()) {
+            this.writeSanitizedAssignmentsToFile(outputFile,
+                formattedSanitizedAssignments);
+          }
+        }
+      }
+    }
+    this.cleanUpRsyncDirectory();
+    logger.info("Finished processing bridge pool assignment file(s).");
+  }
+
+  /**
+   * Initialize configuration by obtaining current configuration values and
+   * storing them in instance attributes.
+   */
+  private void initializeConfiguration() throws ConfigurationException {
+    this.outputPathName = Paths.get(config.getPath(Key.OutputPath).toString(),
+        "bridge-pool-assignments").toString();
+    this.recentPathName = Paths.get(config.getPath(Key.RecentPath).toString(),
+        "bridge-pool-assignments").toString();
+    this.assignmentsDirectory =
+        config.getPath(Key.BridgePoolAssignmentsLocalOrigins).toFile();
+  }
+
+  /**
+   * Compile a list of all assignment files in the input directory.
+   *
+   * @return List of assignment files.
+   */
+  private List<File> listAssignmentFiles() {
+    List<File> assignmentFiles = new ArrayList<>();
+    Stack<File> files = new Stack<>();
+    files.add(this.assignmentsDirectory);
+    while (!files.isEmpty()) {
+      File file = files.pop();
+      if (file.isDirectory()) {
+        File[] filesInDirectory = file.listFiles();
+        if (null != filesInDirectory) {
+          files.addAll(Arrays.asList(filesInDirectory));
+        }
+      } else if (file.getName().startsWith("assignments.log")) {
+        assignmentFiles.add(file);
+      }
+    }
+    return assignmentFiles;
+  }
+
+  /**
+   * Read one or more bridge pool assignments from the given file and store them
+   * in a map with keys being published timestamps and values being maps of
+   * (original, not-yet-sanitized) fingerprints and assignment details.
+   *
+   * @param assignmentFile File containing one or more bridge pool assignments.
+   * @return Map containing all read bridge pool assignments.
+   */
+  private SortedMap<LocalDateTime, SortedMap<String, String>>
+      readBridgePoolAssignments(File assignmentFile) {
+    SortedMap<LocalDateTime, SortedMap<String, String>>
+        readBridgePoolAssignments = new TreeMap<>();
+    try {
+      BufferedReader br;
+      if (assignmentFile.getName().endsWith(".gz")) {
+        br = new BufferedReader(new InputStreamReader(
+            new GzipCompressorInputStream(new FileInputStream(
+                assignmentFile))));
+      } else {
+        br = new BufferedReader(new FileReader(assignmentFile));
+      }
+      String line;
+      SortedMap<String, String> currentAssignments = null;
+      while ((line = br.readLine()) != null) {
+        if (line.startsWith("bridge-pool-assignment ")) {
+          try {
+            LocalDateTime bridgePoolAssignmentTime = LocalDateTime.parse(
+                line.substring("bridge-pool-assignment ".length()),
+                this.assignmentFormat);
+            if (readBridgePoolAssignments.containsKey(
+                bridgePoolAssignmentTime)) {
+              logger.warn("Input file {} contains duplicate line: {}. "
+                  + "Discarding previously read line and subsequent assignment "
+                  + "lines.", assignmentFile, line);
+            }
+            currentAssignments = new TreeMap<>();
+            readBridgePoolAssignments.put(bridgePoolAssignmentTime,
+                currentAssignments);
+          } catch (DateTimeException e) {
+            logger.warn("Could not parse timestamp from line {}. Skipping "
+                    + "bridge pool assignment file '{}'.", line,
+                assignmentFile.getAbsolutePath(), e);
+            break;
+          }
+        } else if (null == currentAssignments) {
+          logger.warn("Input file {} does not start with a "
+              + "bridge-pool-assignments line. Skipping.",
+              assignmentFile);
+          break;
+        } else {
+          String[] parts = line.split(" ", 2);
+          if (parts.length < 2 || parts[0].length() < 40) {
+            logger.warn("Unrecognized line '{}'. Aborting.", line);
+            break;
+          }
+          if (currentAssignments.containsKey(parts[0])) {
+            logger.warn("Input file {} contains duplicate line: {}. "
+                + "Discarding previously read line.", assignmentFile, line);
+          }
+          currentAssignments.put(parts[0], parts[1]);
+        }
+      }
+      br.close();
+    } catch (IOException e) {
+      logger.warn("Could not read bridge pool assignment file '{}'. "
+          + "Skipping.", assignmentFile.getAbsolutePath(), e);
+    }
+    if (!readBridgePoolAssignments.isEmpty()
+        && readBridgePoolAssignments.lastKey().minusMinutes(330L)
+        .isBefore(LocalDateTime.now())) {
+      logger.warn("The last known bridge pool assignment list was "
+          + "published at {}, which is more than 5:30 hours in the past.",
+          readBridgePoolAssignments.lastKey());
+    }
+    return readBridgePoolAssignments;
+  }
+
+  /**
+   * Sanitize the given bridge pool assignments by returning a new map with keys
+   * being SHA-1 digests of keys found in the given map.
+   *
+   * @param originalAssignments Map of (original, not-yet-sanitized)
+   *     fingerprints to assignment details.
+   * @return Map of sanitized fingerprints to assignment details.
+   */
+  private SortedMap<String, String> sanitizeAssignments(
+      SortedMap<String, String> originalAssignments) {
+    SortedMap<String, String> sanitizedAssignments = new TreeMap<>();
+    for (Map.Entry<String, String> e : originalAssignments.entrySet()) {
+      String originalFingerprint = e.getKey();
+      String assignmentDetails = e.getValue();
+      try {
+        String hashedFingerprint = Hex.encodeHexString(DigestUtils.sha1(
+            Hex.decodeHex(originalFingerprint.toCharArray()))).toLowerCase();
+        sanitizedAssignments.put(hashedFingerprint, assignmentDetails);
+      } catch (DecoderException ex) {
+        logger.warn("Unable to decode hex fingerprint. Aborting.", ex);
+        return null;
+      }
+    }
+    return sanitizedAssignments;
+  }
+
+  /**
+   * Format sanitized bridge pool assignments consisting of a published
+   * timestamp and a map of sanitized fingerprints to assignment details as a
+   * single string.
+   *
+   * @param published Published timestamp as found in the bridge-pool-assignment
+   *     line.
+   * @param sanitizedAssignments Map of sanitized fingerprints to assignment
+   *     details.
+   * @return Formatted sanitized bridge pool assignments.
+   */
+  private String formatSanitizedAssignments(LocalDateTime published,
+      SortedMap<String, String> sanitizedAssignments) {
+    StringBuilder sb = new StringBuilder();
+    sb.append("@type bridge-pool-assignment 1.0\n");
+    sb.append(String.format("bridge-pool-assignment %s\n",
+        published.format(this.assignmentFormat)));
+    for (Map.Entry<String, String> e : sanitizedAssignments.entrySet()) {
+      sb.append(String.format("%s %s%n", e.getKey(), e.getValue()));
+    }
+    return sb.toString();
+  }
+
+  /**
+   * Write the given formatted sanitized bridge pool assignments to the given
+   * file, or if that fails for any reason, log a warning and exit.
+   *
+   * @param outputFile File to write to.
+   * @param formattedSanitizedAssignments Formatted sanitized bridge pool
+   *     assignments to write.
+   */
+  private void writeSanitizedAssignmentsToFile(File outputFile,
+      String formattedSanitizedAssignments) {
+    if (!outputFile.getParentFile().exists()
+        && !outputFile.getParentFile().mkdirs()) {
+      logger.warn("Could not create parent directories of {}.", outputFile);
+      return;
+    }
+    try (BufferedWriter bw = new BufferedWriter(new FileWriter(outputFile))) {
+      bw.write(formattedSanitizedAssignments);
+    } catch (IOException e) {
+      logger.warn("Unable to write sanitized bridge pool assignments to {}.",
+          outputFile, e);
+    }
+  }
+
+  /**
+   * Delete all files from the rsync directory that have not been modified in
+   * the last three days.
+   */
+  public void cleanUpRsyncDirectory() {
+    Instant cutOff = Instant.now().minus(3L, ChronoUnit.DAYS);
+    Stack<File> allFiles = new Stack<>();
+    allFiles.add(new File(this.recentPathName));
+    while (!allFiles.isEmpty()) {
+      File file = allFiles.pop();
+      if (file.isDirectory()) {
+        File[] filesInDirectory = file.listFiles();
+        if (null != filesInDirectory) {
+          allFiles.addAll(Arrays.asList(filesInDirectory));
+        }
+      } else if (Instant.ofEpochMilli(file.lastModified()).isBefore(cutOff)) {
+        try {
+          Files.deleteIfExists(file.toPath());
+        } catch (IOException e) {
+          logger.warn("Unable to delete file {} that is apparently older than "
+              + "three days.", file, e);
+        }
+      }
+    }
+  }
+}
+
+
diff --git a/src/main/java/org/torproject/metrics/collector/conf/Annotation.java b/src/main/java/org/torproject/metrics/collector/conf/Annotation.java
index 8cd3324..7d2bbe9 100644
--- a/src/main/java/org/torproject/metrics/collector/conf/Annotation.java
+++ b/src/main/java/org/torproject/metrics/collector/conf/Annotation.java
@@ -8,6 +8,7 @@ public enum Annotation {
 
   BandwidthFile("@type bandwidth-file 1.0\n"),
   BridgeExtraInfo("@type bridge-extra-info 1.3\n"),
+  BridgePoolAssignment("@type bridge-pool-assignment 1.0\n"),
   BridgeServer("@type bridge-server-descriptor 1.2\n"),
   Cert("@type dir-key-certificate-3 1.0\n"),
   Consensus("@type network-status-consensus-3 1.0\n"),
diff --git a/src/main/java/org/torproject/metrics/collector/conf/Configuration.java b/src/main/java/org/torproject/metrics/collector/conf/Configuration.java
index 27f5125..59229e3 100644
--- a/src/main/java/org/torproject/metrics/collector/conf/Configuration.java
+++ b/src/main/java/org/torproject/metrics/collector/conf/Configuration.java
@@ -88,6 +88,7 @@ public class Configuration extends Observable implements Cloneable {
   private void anythingActivated() throws ConfigurationException {
     if (!(this.getBool(Key.RelaydescsActivated)
         || this.getBool(Key.BridgedescsActivated)
+        || this.getBool(Key.BridgePoolAssignmentsActivated)
         || this.getBool(Key.ExitlistsActivated)
         || this.getBool(Key.UpdateindexActivated)
         || this.getBool(Key.OnionPerfActivated)
diff --git a/src/main/java/org/torproject/metrics/collector/conf/Key.java b/src/main/java/org/torproject/metrics/collector/conf/Key.java
index e683fe2..dfef673 100644
--- a/src/main/java/org/torproject/metrics/collector/conf/Key.java
+++ b/src/main/java/org/torproject/metrics/collector/conf/Key.java
@@ -27,6 +27,7 @@ public enum Key {
   SyncPath(Path.class),
   RelaySources(SourceType[].class),
   BridgeSources(SourceType[].class),
+  BridgePoolAssignmentsSources(SourceType[].class),
   ExitlistSources(SourceType[].class),
   OnionPerfSources(SourceType[].class),
   WebstatsSources(SourceType[].class),
@@ -35,6 +36,8 @@ public enum Key {
   RelaySyncOrigins(URL[].class),
   BridgeSyncOrigins(URL[].class),
   BridgeLocalOrigins(Path.class),
+  BridgePoolAssignmentsLocalOrigins(Path.class),
+  BridgePoolAssignmentsSyncOrigins(URL[].class),
   ExitlistSyncOrigins(URL[].class),
   OnionPerfSyncOrigins(URL[].class),
   WebstatsSyncOrigins(URL[].class),
@@ -42,6 +45,9 @@ public enum Key {
   BridgedescsActivated(Boolean.class),
   BridgedescsOffsetMinutes(Integer.class),
   BridgedescsPeriodMinutes(Integer.class),
+  BridgePoolAssignmentsActivated(Boolean.class),
+  BridgePoolAssignmentsOffsetMinutes(Integer.class),
+  BridgePoolAssignmentsPeriodMinutes(Integer.class),
   ExitlistsActivated(Boolean.class),
   ExitlistsOffsetMinutes(Integer.class),
   ExitlistsPeriodMinutes(Integer.class),
diff --git a/src/main/java/org/torproject/metrics/collector/persist/BridgePoolAssignmentPersistence.java b/src/main/java/org/torproject/metrics/collector/persist/BridgePoolAssignmentPersistence.java
new file mode 100644
index 0000000..5613060
--- /dev/null
+++ b/src/main/java/org/torproject/metrics/collector/persist/BridgePoolAssignmentPersistence.java
@@ -0,0 +1,34 @@
+/* Copyright 2016--2018 The Tor Project
+ * See LICENSE for licensing information */
+
+package org.torproject.metrics.collector.persist;
+
+import org.torproject.descriptor.BridgePoolAssignment;
+import org.torproject.metrics.collector.conf.Annotation;
+
+import java.nio.file.Paths;
+
+public class BridgePoolAssignmentPersistence
+    extends DescriptorPersistence<BridgePoolAssignment> {
+
+  public BridgePoolAssignmentPersistence(BridgePoolAssignment desc) {
+    super(desc, Annotation.BridgePoolAssignment.bytes());
+    calculatePaths();
+  }
+
+  private void calculatePaths() {
+    String file = PersistenceUtils.dateTime(desc.getPublishedMillis());
+    String[] parts = file.split(DASH);
+    this.recentPath = Paths.get(
+        BRIDGEPOOLASSIGNMENTS,
+        file).toString();
+    this.storagePath = Paths.get(
+        BRIDGEPOOLASSIGNMENTS,
+        parts[0], // year
+        parts[1], // month
+        parts[2], // day
+        file).toString();
+  }
+
+}
+
diff --git a/src/main/java/org/torproject/metrics/collector/persist/DescriptorPersistence.java b/src/main/java/org/torproject/metrics/collector/persist/DescriptorPersistence.java
index fed4839..3e7a06b 100644
--- a/src/main/java/org/torproject/metrics/collector/persist/DescriptorPersistence.java
+++ b/src/main/java/org/torproject/metrics/collector/persist/DescriptorPersistence.java
@@ -18,6 +18,8 @@ public abstract class DescriptorPersistence<T extends Descriptor> {
       DescriptorPersistence.class);
 
   protected static final String BRIDGEDESCS = "bridge-descriptors";
+  protected static final String BRIDGEPOOLASSIGNMENTS
+      = "bridge-pool-assignments";
   protected static final String DASH = "-";
   protected static final String DOT = ".";
   protected static final String MICRODESC = "microdesc";
diff --git a/src/main/java/org/torproject/metrics/collector/sync/SyncPersistence.java b/src/main/java/org/torproject/metrics/collector/sync/SyncPersistence.java
index 4b3b7bc..cfc3dbe 100644
--- a/src/main/java/org/torproject/metrics/collector/sync/SyncPersistence.java
+++ b/src/main/java/org/torproject/metrics/collector/sync/SyncPersistence.java
@@ -6,6 +6,7 @@ package org.torproject.metrics.collector.sync;
 import org.torproject.descriptor.BandwidthFile;
 import org.torproject.descriptor.BridgeExtraInfoDescriptor;
 import org.torproject.descriptor.BridgeNetworkStatus;
+import org.torproject.descriptor.BridgePoolAssignment;
 import org.torproject.descriptor.BridgeServerDescriptor;
 import org.torproject.descriptor.Descriptor;
 import org.torproject.descriptor.ExitList;
@@ -21,6 +22,7 @@ import org.torproject.metrics.collector.conf.ConfigurationException;
 import org.torproject.metrics.collector.conf.Key;
 import org.torproject.metrics.collector.persist.BandwidthFilePersistence;
 import org.torproject.metrics.collector.persist.BridgeExtraInfoPersistence;
+import org.torproject.metrics.collector.persist.BridgePoolAssignmentPersistence;
 import org.torproject.metrics.collector.persist.BridgeServerDescriptorPersistence;
 import org.torproject.metrics.collector.persist.ConsensusPersistence;
 import org.torproject.metrics.collector.persist.DescriptorPersistence;
@@ -132,6 +134,10 @@ public class SyncPersistence {
           descPersist = new BridgeServerDescriptorPersistence(
               (BridgeServerDescriptor) desc, received);
           break;
+        case "BridgePoolAssignment":
+          descPersist = new BridgePoolAssignmentPersistence(
+              (BridgePoolAssignment) desc);
+          break;
         case "ExitList": // downloaded is part of desc, which to use?
           descPersist = new ExitlistPersistence((ExitList) desc, received);
           break;
diff --git a/src/main/resources/collector.properties b/src/main/resources/collector.properties
index a4eed7a..b180a3e 100644
--- a/src/main/resources/collector.properties
+++ b/src/main/resources/collector.properties
@@ -18,6 +18,12 @@ BridgedescsPeriodMinutes = 60
 # offset in minutes since the epoch and
 BridgedescsOffsetMinutes = 9
 ## the following defines, if this module is activated
+BridgePoolAssignmentsActivated = false
+# period in minutes
+BridgePoolAssignmentsPeriodMinutes = 60
+# offset in minutes since the epoch and
+BridgePoolAssignmentsOffsetMinutes = 9
+## the following defines, if this module is activated
 ExitlistsActivated = false
 # period in minutes
 ExitlistsPeriodMinutes = 60
@@ -146,6 +152,18 @@ ReplaceIpAddressesWithHashes = false
 BridgeDescriptorMappingsLimit = inf
 #
 #
+######## Bridge pool assignments ########
+#
+## Define descriptor sources
+#  possible values: Sync, Local
+BridgePoolAssignmentsSources = Local
+##  Retrieve files from the following instances.
+##  List of URLs separated by comma.
+BridgePoolAssignmentsSyncOrigins = https://collector.torproject.org
+## Relative path to directory to read bridge pool assignment files from
+BridgePoolAssignmentsLocalOrigins = in/bridge-pool-assignments/
+#
+#
 ######## Exit lists ########
 #
 ## Define descriptor sources
diff --git a/src/test/java/org/torproject/metrics/collector/conf/ConfigurationTest.java b/src/test/java/org/torproject/metrics/collector/conf/ConfigurationTest.java
index 3a69c0c..201d541 100644
--- a/src/test/java/org/torproject/metrics/collector/conf/ConfigurationTest.java
+++ b/src/test/java/org/torproject/metrics/collector/conf/ConfigurationTest.java
@@ -38,7 +38,7 @@ public class ConfigurationTest {
   public void testKeyCount() {
     assertEquals("The number of properties keys in enum Key changed."
         + "\n This test class should be adapted.",
-        59, Key.values().length);
+        65, Key.values().length);
   }
 
   @Test()
diff --git a/src/test/java/org/torproject/metrics/collector/cron/CollecTorMainTest.java b/src/test/java/org/torproject/metrics/collector/cron/CollecTorMainTest.java
index d0fe173..99f1f48 100644
--- a/src/test/java/org/torproject/metrics/collector/cron/CollecTorMainTest.java
+++ b/src/test/java/org/torproject/metrics/collector/cron/CollecTorMainTest.java
@@ -69,6 +69,7 @@ public class CollecTorMainTest {
       switch (marker) {
         case "Relay":
         case "Bridge":
+        case "BridgePoolAssignments":
         case "Exitlist":
         case "OnionPerf":
         case "Webstats":





More information about the tor-commits mailing list