Merge branch '15684-package-deps' refs #15684
authorPeter Amstutz <pamstutz@veritasgenetics.com>
Thu, 3 Oct 2019 22:01:46 +0000 (18:01 -0400)
committerPeter Amstutz <pamstutz@veritasgenetics.com>
Thu, 3 Oct 2019 22:01:46 +0000 (18:01 -0400)
Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <pamstutz@veritasgenetics.com>

31 files changed:
apps/workbench/app/views/projects/_show_dashboard.html.erb
apps/workbench/test/controllers/projects_controller_test.rb
apps/workbench/test/integration/application_layout_test.rb
apps/workbench/test/integration/logins_test.rb
apps/workbench/test/integration/user_profile_test.rb
apps/workbench/test/integration/work_units_test.rb
doc/_config.yml
doc/admin/collection-versioning.html.textile.liquid
doc/admin/config-migration.html.textile.liquid
doc/admin/keep-balance.html.textile.liquid [new file with mode: 0644]
doc/admin/upgrading.html.textile.liquid
doc/install/install-keep-balance.html.textile.liquid
doc/install/install-keepstore.html.textile.liquid
lib/config/config.default.yml
lib/config/deprecated.go
lib/config/deprecated_test.go
lib/config/export.go
lib/config/generated_config.go
lib/config/load.go
sdk/go/arvados/config.go
services/keep-balance/balance.go
services/keep-balance/balance_run_test.go
services/keep-balance/balance_test.go
services/keep-balance/collection_test.go
services/keep-balance/integration_test.go
services/keep-balance/keep-balance.service
services/keep-balance/main.go
services/keep-balance/main_test.go [deleted file]
services/keep-balance/metrics.go
services/keep-balance/server.go
services/keep-balance/usage.go [deleted file]

index 71ef2454190d7625cae12cf77d44f05a1ad7ed27..6c58cd30fc723f7a662b2b6b6728cb719519136b 100644 (file)
@@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0 %>
 
 <%
   recent_procs_panel_width = 6
-  if !PipelineInstance.api_exists?(:index)
+  if !PipelineInstance.api_exists?(:create)
     recent_procs_title = 'Recent processes'
     run_proc_title = 'Choose a workflow to run:'
     show_node_status = false
index 0f79168901364f72c232c85cd981eb0540efd084..dd828952be2fb828e8f1e1b11b1cfc1616515683 100644 (file)
@@ -396,10 +396,7 @@ EOT
     end
   end
 
-  [
-    [:admin, true],
-    [:active, false],
-  ].each do |user, expect_all_nodes|
+  [:admin, :active].each do |user|
     test "in dashboard other index page links as #{user}" do
       get :index, params: {}, session: session_for(user)
 
@@ -409,14 +406,6 @@ EOT
         assert_includes @response.body, "href=\"#{path}\""
         assert_includes @response.body, "All #{target}"
       end
-
-      if expect_all_nodes
-        assert_includes @response.body, "href=\"/nodes\""
-        assert_includes @response.body, "All nodes"
-      else
-        assert_not_includes @response.body, "href=\"/nodes\""
-        assert_not_includes @response.body, "All nodes"
-      end
     end
   end
 
index dc958d3b5e23295bd2013de9c0a4af9db419b0d0..e28809e1318ba42c572d9f1a3eca94387d9a39b2 100644 (file)
@@ -214,7 +214,7 @@ class ApplicationLayoutTest < ActionDispatch::IntegrationTest
 
         first('button', text: 'x').click
       end
-      assert_text 'Recent pipelines and processes' # seeing dashboard now
+      assert_text 'Recent processes' # seeing dashboard now
     end
   end
 
@@ -285,7 +285,7 @@ class ApplicationLayoutTest < ActionDispatch::IntegrationTest
     test "visit dashboard as #{token}" do
       visit page_with_token(token)
 
-      assert_text 'Recent pipelines and processes' # seeing dashboard now
+      assert_text 'Recent processes' # seeing dashboard now
       within('.recent-processes-actions') do
         assert page.has_link?('Run a process')
         assert page.has_link?('All processes')
@@ -307,19 +307,6 @@ class ApplicationLayoutTest < ActionDispatch::IntegrationTest
           assert page.has_link? 'foo_file'
         end
       end
-
-      within('.compute-node-actions') do
-        if is_admin
-          assert page.has_link?('All nodes')
-        else
-          assert page.has_no_link?('All nodes')
-        end
-      end
-
-      within('.compute-node-summary-pane') do
-        click_link 'Details'
-        assert_text 'compute0'
-      end
     end
   end
 end
index 7f2774ce2f33beb7d596a32dc89569c984ff17f2..f079fbb8f1ce39eb4d88e33f232c3f8796dce89c 100644 (file)
@@ -11,7 +11,7 @@ class LoginsTest < ActionDispatch::IntegrationTest
 
   test "login with api_token works after redirect" do
     visit page_with_token('active_trustedclient')
-    assert page.has_text?('Recent pipelines and processes'), "Missing 'Recent pipelines and processes' from page"
+    assert page.has_text?('Recent processes'), "Missing 'Recent processes' from page"
     assert_no_match(/\bapi_token=/, current_path)
   end
 
index 547ef06a6827f013b0e958226e10d87ef57464fe..30d4943c62018473822877b475023544f73ac503 100644 (file)
@@ -24,7 +24,7 @@ class UserProfileTest < ActionDispatch::IntegrationTest
         assert_text('Save profile')
         add_profile user
       else
-        assert_text('Recent pipelines and processes')
+        assert_text('Recent processes')
         assert_no_text('Save profile')
       end
     elsif invited
@@ -126,7 +126,7 @@ class UserProfileTest < ActionDispatch::IntegrationTest
     end
 
     # profile saved and in home page now
-    assert_text('Recent pipelines and processes')
+    assert_text('Recent processes')
   end
 
   [
index fe73f2734f3d9473a82c9ddb7e11498613ad645d..9d4f5905553d96f9fbc3500009dad5d11bdd49b7 100644 (file)
@@ -145,7 +145,7 @@ class WorkUnitsTest < ActionDispatch::IntegrationTest
   ].each do |template_name, preview_txt, process_txt|
     test "run a process using template #{template_name} from dashboard" do
       visit page_with_token('admin')
-      assert_text 'Recent pipelines and processes' # seeing dashboard now
+      assert_text 'Recent processes' # seeing dashboard now
 
       within('.recent-processes-actions') do
         assert page.has_link?('All processes')
index 0547c8ee9366f6788fbe87b34c5b319638a0734f..c4fad997f17afb31327a17e8a38695f22a701934 100644 (file)
@@ -172,6 +172,7 @@ navbar:
     - Data Management:
       - admin/collection-versioning.html.textile.liquid
       - admin/collection-managed-properties.html.textile.liquid
+      - admin/keep-balance.html.textile.liquid
     - Other:
       - admin/federation.html.textile.liquid
       - admin/controlling-container-reuse.html.textile.liquid
index 6da1756b5ce94bc2f1e624a290aabe3bfb3f720e..0a4d1fa769ac14b691d05bff673a9487df9559c1 100644 (file)
@@ -16,7 +16,7 @@ h3. API Server configuration
 
 There are 2 configuration settings that control this feature, both go on the @application.yml@ file.
 
-h4. Settting: @collection_versioning@ (Boolean. Default: false)
+h4. Setting: @collection_versioning@ (Boolean. Default: false)
 
 If @true@, collection versioning is enabled, meaning that new version records can be created. Note that if you set @collection_versioning@ to @false@ after being enabled, old versions will still be accessible, but further changes will not be versioned.
 
index b2ca4fd0b8709ce507add3ec54f2026d673e9301..d40cd3bbdc5feeb548693d60d820aa75fd3fee7b 100644 (file)
@@ -74,6 +74,9 @@ h2(#arv-git-httpd). arv-git-httpd
 
 The legacy arv-git-httpd config (loaded from @/etc/arvados/git-httpd/git-httpd.yml@ or a different location specified via -legacy-git-httpd-config command line argument) takes precedence over the centralized config. After you migrate everything from the legacy config to the centralized config, you should delete @/etc/arvados/git-httpd/git-httpd.yml@ and stop using the -legacy-git-httpd-config argument.
 
+h2(#keepbalance). keep-balance
+
+The legacy keep-balance config (loaded from @/etc/arvados/keep-balance/keep-balance.yml@ or a different location specified via -legacy-keepbalance-config command line argument) takes precedence over the centralized config. After you migrate everything from the legacy config to the centralized config, you should delete @/etc/arvados/keep-balance/keep-balance.yml@ and stop using the -legacy-keepbalance-config argument.
 
 h2. arvados-controller
 
diff --git a/doc/admin/keep-balance.html.textile.liquid b/doc/admin/keep-balance.html.textile.liquid
new file mode 100644 (file)
index 0000000..5af0a26
--- /dev/null
@@ -0,0 +1,43 @@
+---
+layout: default
+navsection: admin
+title: Balancing Keep servers
+...
+
+{% comment %}
+Copyright (C) The Arvados Authors. All rights reserved.
+
+SPDX-License-Identifier: CC-BY-SA-3.0
+{% endcomment %}
+
+This page describes how to balance keepstore servers using keep-balance. Keep-balance creates new copies of under-replicated blocks, deletes excess copies of over-replicated and unreferenced blocks, and moves blocks to better positions (e.g. after adding new keepstore servers) so clients find them faster.
+
+See "the Keep-balance install docs":{{site.baseurl}}/install/install-keep-balance.html for installation instructions.
+
+h3. Data deletion
+
+The keep-balance service determines which blocks are candidates for deletion and instructs the keepstore to move those blocks to the trash. When a block is newly written, it is protected from deletion for the duration in @BlobSigningTTL@.  During this time, it cannot be trashed or deleted.
+
+If keep-balance instructs keepstore to trash a block which is older than @BlobSigningTTL@, and @BlobTrashLifetime@ is non-zero, the block will be moved to "trash".  A block which is in the trash is no longer accessible by read requests, but has not yet been permanently deleted.  Blocks which are in the trash may be recovered using the "untrash" API endpoint.  Blocks are permanently deleted after they have been in the trash for the duration in @BlobTrashLifetime@.
+
+Keep-balance is also responsible for balancing the distribution of blocks across keepstore servers by asking servers to pull blocks from other servers (as determined by their "storage class":{{site.baseurl}}/admin/storage-classes.html and "rendezvous hashing order":{{site.baseurl}}/api/storage.html).  Pulling a block makes a copy.  If a block is overreplicated (i.e. there are excess copies) after pulling, it will be subsequently trashed and deleted on the original server, subject to @BlobTrash@ and @BlobTrashLifetime@ settings.
+
+h3. Scanning
+
+By default, keep-balance operates periodically, i.e. do a scan/balance operation, sleep, repeat.
+
+The @Collections.BalancePeriod@ value in @/etc/arvados/config.yml@ determines the interval between start times of successive scan/balance operations. If an operation takes longer than the @Collections.BalancePeriod@, the next operation will follow it immediately. If SIGUSR1 is received during an idle period between operations, the next operation will start immediately.
+
+Keep-balance can also be run with the @-once@ flag to do a single scan/balance operation and then exit. The exit code will be zero if the operation was successful.
+
+h3. Committing
+
+Keep-balance computes and reports changes but does not implement them by sending pull and trash lists to the Keep services unless the @-commit-pull@ and @-commit-trash@ flags are used.
+
+h3. Additional configuration
+
+For configuring resource usage tuning and lost block reporting, please see the @Collections.BlobMissingReport@, @Collections.BalanceCollectionBatch@, @Collections.BalanceCollectionBuffers@ option in the "default config.yml file":{{site.baseurl}}/admin/config.html.
+
+h3. Limitations
+
+Keep-balance does not attempt to discover whether committed pull and trash requests ever get carried out -- only that they are accepted by the Keep services. If some services are full, new copies of under-replicated blocks might never get made, only repeatedly requested.
\ No newline at end of file
index 5faf79cde7a57f317b7f5efe9fbf6f3f17632c54..376d7abc07f528c4e87249edf21fe96c364bd796 100644 (file)
@@ -52,6 +52,16 @@ $ arv --format=uuid keep_service list | xargs -n1 arv keep_service delete --uuid
 
 Once these old records are removed, @arv keep_service list@ will instead return the services listed under Services/Keepstore/InternalURLs and Services/Keepproxy/ExternalURL in your centralized configuration file.
 
+h4. Keep-balance configuration migration
+
+(feature "#14714":https://dev.arvados.org/issues/14714 ) The keep-balance service can now be configured using the centralized configuration file at @/etc/arvados/config.yml@. The following command line and configuration options have changed.
+
+You can no longer specify types of keep services to balance via the @KeepServiceTypes@ config option in the legacy config at @/etc/arvados/keep-balance/keep-balance.yml@. If you are still using the legacy config and @KeepServiceTypes@ has a value other than "disk", keep-balance will produce an error.
+
+You can no longer specify individual keep services to balance via the @config.KeepServiceList@ command line option or @KeepServiceList@ legacy config option. Instead, keep-balance will operate on all keepstore servers with @service_type:disk@ as reported by the @arv keep_service list@ command. If you are still using the legacy config, @KeepServiceList@ should be removed or keep-balance will produce an error.
+
+Please see the "config migration guide":{{site.baseurl}}/admin/config-migration.html and "keep-balance install guide":{{site.baseurl}}/install/install-keep-balance.html for more details.
+
 h4. Arv-git-httpd configuration migration
 
 (feature "#14712":https://dev.arvados.org/issues/14712 ) The arv-git-httpd package can now be configured using the centralized configuration file at @/etc/arvados/config.yml@. Configuration via individual command line arguments is no longer available. Please see "arv-git-httpd's config migration guide":{{site.baseurl}}/admin/config-migration.html#arv-git-httpd for more details.
index a7f31dfe5fd4f35de08df2b07d247812af4252e7..d29166459c95bd307e555eee42f6cbd82e1be323 100644 (file)
@@ -9,7 +9,7 @@ Copyright (C) The Arvados Authors. All rights reserved.
 SPDX-License-Identifier: CC-BY-SA-3.0
 {% endcomment %}
 
-Keep-balance deletes unreferenced and overreplicated blocks from Keep servers, makes additional copies of underreplicated blocks, and moves blocks into optimal locations as needed (e.g., after adding new servers).
+Keep-balance deletes unreferenced and overreplicated blocks from Keep servers, makes additional copies of underreplicated blocks, and moves blocks into optimal locations as needed (e.g., after adding new servers). See "Balancing Keep servers":{{site.baseurl}}/admin/keep-balance.html for usage details.
 
 {% include 'notebox_begin' %}
 
@@ -42,44 +42,32 @@ Verify that @keep-balance@ is functional:
 <notextile>
 <pre><code>~$ <span class="userinput">keep-balance -h</span>
 ...
-Usage: keep-balance [options]
-
-Options:
+Usage of ./keep-balance:
   -commit-pulls
-        send pull requests (make more replicas of blocks that are underreplicated or are not in optimal rendezvous probe order)
+       send pull requests (make more replicas of blocks that are underreplicated or are not in optimal rendezvous probe order)
   -commit-trash
-        send trash requests (delete unreferenced old blocks, and excess replicas of overreplicated blocks)
+       send trash requests (delete unreferenced old blocks, and excess replicas of overreplicated blocks)
 ...
 </code></pre>
 </notextile>
 
-h3. Create a keep-balance configuration file
+h3. Update the cluster config
 
-On the host running keep-balance, create @/etc/arvados/keep-balance/keep-balance.yml@ using the SystemRootToken from your cluster configuration file.  Follow this YAML format:
+Edit the cluster config at @/etc/arvados/config.yml@ and set @Services.Keepbalance.InternalURLs@. Replace @uuid_prefix@ with your cluster id.
 
 <notextile>
-<pre><code>Listen: :9005
-Client:
-  APIHost: <span class="userinput">uuid_prefix.your.domain</span>:443
-  AuthToken: zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
-KeepServiceTypes:
-  - disk
-ManagementToken: <span class="userinput">xyzzy</span>
-RunPeriod: 10m
-CollectionBatchSize: 100000
-CollectionBuffers: 1000
-LostBlocksFile: /tmp/keep-balance-lost-blocks.txt    # If given, this file will be updated atomically during each successful run.
+<pre><code>Clusters:
+  <span class="userinput">uuid_prefix</span>:
+    Services:
+      Keepbalance:
+        InternalURLs:
+          "http://localhost:9005/": {}
+    TLS:
+      Insecure: false
 </code></pre>
 </notextile>
 
-If your API server's SSL certificate is not signed by a recognized CA, add the @Insecure@ option to the @Client@ section:
-
-<notextile>
-<pre><code>Client:
-  <span class="userinput">Insecure: true</span>
-  APIHost: ...
-</code></pre>
-</notextile>
+Set @TLS.Insecure: true@ if your API server’s TLS certificate is not signed by a recognized CA.
 
 h3. Start the service (option 1: systemd)
 
index a16de51627f72241b1200875ebdf1dfc0a2eec2e..71c1cb639e5afd26edf083685a11d7f043ea15cb 100644 (file)
@@ -79,15 +79,9 @@ Add or update the following sections of @/etc/arvados/config.yml@ as needed. Ref
 </code></pre>
 </notextile>
 
-h3. Notes on storage management
+h3. Note on storage management
 
-On its own, a keepstore server never deletes data.  The "keep-balance":install-keep-balance.html service determines which blocks are candidates for deletion and instructs the keepstore to move those blocks to the trash.
-
-When a block is newly written, it is protected from deletion for the duration in @BlobSigningTTL@.  During this time, it cannot be trashed or deleted.
-
-If keep-balance instructs keepstore to trash a block which is older than @BlobSigningTTL@, and @BlobTrashLifetime@ is non-zero, the block will be moved to "trash".  A block which is in the trash is no longer accessible by read requests, but has not yet been permanently deleted.  Blocks which are in the trash may be recovered using the "untrash" API endpoint.  Blocks are permanently deleted after they have been in the trash for the duration in @BlobTrashLifetime@.
-
-Keep-balance is also responsible for balancing the distribution of blocks across keepstore servers by asking servers to pull blocks from other servers (as determined by their "storage class":{{site.baseurl}}/admin/storage-classes.html and "rendezvous hashing order":{{site.baseurl}}/api/storage.html).  Pulling a block makes a copy.  If a block is overreplicated (i.e. there are excess copies) after pulling, it will be subsequently trashed and deleted on the original server, subject to @BlobTrash@ and @BlobTrashLifetime@ settings.
+On its own, a keepstore server never deletes data. Instead, the keep-balance service determines which blocks are candidates for deletion and instructs the keepstore to move those blocks to the trash. Please see the "Balancing Keep servers":{{site.baseurl}}/admin/keep-balance.html for more details.
 
 h3. Configure storage volumes
 
index 7c22233178fde187a0423f18d0fcabcdc5f21861..4e3bf6d6c937d89ccdc8cdd1781e5f3d76425965 100644 (file)
@@ -368,13 +368,26 @@ Clusters:
       # collection's replication_desired attribute is nil.
       DefaultReplication: 2
 
-      # Lifetime (in seconds) of blob permission signatures generated by
-      # the API server. This determines how long a client can take (after
-      # retrieving a collection record) to retrieve the collection data
-      # from Keep. If the client needs more time than that (assuming the
-      # collection still has the same content and the relevant user/token
-      # still has permission) the client can retrieve the collection again
-      # to get fresh signatures.
+      # BlobSigningTTL determines the minimum lifetime of transient
+      # data, i.e., blocks that are not referenced by
+      # collections. Unreferenced blocks exist for two reasons:
+      #
+      # 1) A data block must be written to a disk/cloud backend device
+      # before a collection can be created/updated with a reference to
+      # it.
+      #
+      # 2) Deleting or updating a collection can remove the last
+      # remaining reference to a data block.
+      #
+      # If BlobSigningTTL is too short, long-running
+      # processes/containers will fail when they take too long (a)
+      # between writing blocks and writing collections that reference
+      # them, or (b) between reading collections and reading the
+      # referenced blocks.
+      #
+      # If BlobSigningTTL is too long, data will still be stored long
+      # after the referring collections are deleted, and you will
+      # needlessly fill up disks or waste money on cloud storage.
       #
       # Modifying BlobSigningTTL invalidates existing signatures; see
       # BlobSigningKey note above.
@@ -382,6 +395,36 @@ Clusters:
       # The default is 2 weeks.
       BlobSigningTTL: 336h
 
+      # When running keep-balance, this is the destination filename for
+      # the list of lost block hashes if there are any, one per line.
+      # Updated automically during each successful run.
+      BlobMissingReport: ""
+
+      # keep-balance operates periodically, i.e.: do a
+      # scan/balance operation, sleep, repeat.
+      #
+      # BalancePeriod determines the interval between start times of
+      # successive scan/balance operations. If a scan/balance operation
+      # takes longer than RunPeriod, the next one will follow it
+      # immediately.
+      #
+      # If SIGUSR1 is received during an idle period between operations,
+      # the next operation will start immediately.
+      BalancePeriod: 10m
+
+      # Limits the number of collections retrieved by keep-balance per
+      # API transaction. If this is zero, page size is
+      # determined by the API server's own page size limits (see
+      # API.MaxItemsPerResponse and API.MaxIndexDatabaseRead).
+      BalanceCollectionBatch: 0
+
+      # The size of keep-balance's internal queue of
+      # collections. Higher values use more memory and improve throughput
+      # by allowing keep-balance to fetch the next page of collections
+      # while the current page is still being processed. If this is zero
+      # or omitted, pages are processed serially.
+      BalanceCollectionBuffers: 1000
+
       # Default lifetime for ephemeral collections: 2 weeks. This must not
       # be less than BlobSigningTTL.
       DefaultTrashLifetime: 336h
index d0e61dbca06454d3488b1f50473b55f67b2a66b1..22eed080a2ca041103d40f157a4645a9ece85b3e 100644 (file)
@@ -476,6 +476,81 @@ func (ldr *Loader) loadOldGitHttpdConfig(cfg *arvados.Config) error {
        return nil
 }
 
+const defaultKeepBalanceConfigPath = "/etc/arvados/keep-balance/keep-balance.yml"
+
+type oldKeepBalanceConfig struct {
+       Client              *arvados.Client
+       Listen              *string
+       KeepServiceTypes    *[]string
+       KeepServiceList     *arvados.KeepServiceList
+       RunPeriod           *arvados.Duration
+       CollectionBatchSize *int
+       CollectionBuffers   *int
+       RequestTimeout      *arvados.Duration
+       LostBlocksFile      *string
+       ManagementToken     *string
+}
+
+func (ldr *Loader) loadOldKeepBalanceConfig(cfg *arvados.Config) error {
+       if ldr.KeepBalancePath == "" {
+               return nil
+       }
+       var oc oldKeepBalanceConfig
+       err := ldr.loadOldConfigHelper("keep-balance", ldr.KeepBalancePath, &oc)
+       if os.IsNotExist(err) && ldr.KeepBalancePath == defaultKeepBalanceConfigPath {
+               return nil
+       } else if err != nil {
+               return err
+       }
+
+       cluster, err := cfg.GetCluster("")
+       if err != nil {
+               return err
+       }
+
+       loadOldClientConfig(cluster, oc.Client)
+
+       if oc.Listen != nil {
+               cluster.Services.Keepbalance.InternalURLs[arvados.URL{Host: *oc.Listen}] = arvados.ServiceInstance{}
+       }
+       if oc.ManagementToken != nil {
+               cluster.ManagementToken = *oc.ManagementToken
+       }
+       if oc.RunPeriod != nil {
+               cluster.Collections.BalancePeriod = *oc.RunPeriod
+       }
+       if oc.LostBlocksFile != nil {
+               cluster.Collections.BlobMissingReport = *oc.LostBlocksFile
+       }
+       if oc.CollectionBatchSize != nil {
+               cluster.Collections.BalanceCollectionBatch = *oc.CollectionBatchSize
+       }
+       if oc.CollectionBuffers != nil {
+               cluster.Collections.BalanceCollectionBuffers = *oc.CollectionBuffers
+       }
+       if oc.RequestTimeout != nil {
+               cluster.API.KeepServiceRequestTimeout = *oc.RequestTimeout
+       }
+
+       msg := "The %s configuration option is no longer supported. Please remove it from your configuration file. See the keep-balance upgrade notes at https://doc.arvados.org/admin/upgrading.html for more details."
+
+       // If the keep service type provided is "disk" silently ignore it, since
+       // this is what ends up being done anyway.
+       if oc.KeepServiceTypes != nil {
+               numTypes := len(*oc.KeepServiceTypes)
+               if numTypes != 0 && !(numTypes == 1 && (*oc.KeepServiceTypes)[0] == "disk") {
+                       return fmt.Errorf(msg, "KeepServiceType")
+               }
+       }
+
+       if oc.KeepServiceList != nil {
+               return fmt.Errorf(msg, "KeepServiceList")
+       }
+
+       cfg.Clusters[cluster.ClusterID] = *cluster
+       return nil
+}
+
 func (ldr *Loader) loadOldEnvironmentVariables(cfg *arvados.Config) error {
        if os.Getenv("ARVADOS_API_TOKEN") == "" && os.Getenv("ARVADOS_API_HOST") == "" {
                return nil
index ea9b50d035483ab7fd0463669ab4dfda591bdc3f..ff1bb9434a42c8babc3cedef9165e7ad3d16d949 100644 (file)
@@ -216,3 +216,60 @@ func (s *LoadSuite) TestLegacyArvGitHttpdConfig(c *check.C) {
        c.Check(cluster.Git.Repositories, check.Equals, "/test/reporoot")
        c.Check(cluster.Services.Keepproxy.InternalURLs[arvados.URL{Host: ":9000"}], check.Equals, arvados.ServiceInstance{})
 }
+
+func (s *LoadSuite) TestLegacyKeepBalanceConfig(c *check.C) {
+       f := "-legacy-keepbalance-config"
+       content := []byte(fmtKeepBalanceConfig(""))
+       cluster, err := testLoadLegacyConfig(content, f, c)
+
+       c.Check(err, check.IsNil)
+       c.Check(cluster, check.NotNil)
+       c.Check(cluster.ManagementToken, check.Equals, "xyzzy")
+       c.Check(cluster.Services.Keepbalance.InternalURLs[arvados.URL{Host: ":80"}], check.Equals, arvados.ServiceInstance{})
+       c.Check(cluster.Collections.BalanceCollectionBuffers, check.Equals, 1000)
+       c.Check(cluster.Collections.BalanceCollectionBatch, check.Equals, 100000)
+       c.Check(cluster.Collections.BalancePeriod.String(), check.Equals, "10m")
+       c.Check(cluster.Collections.BlobMissingReport, check.Equals, "testfile")
+       c.Check(cluster.API.KeepServiceRequestTimeout.String(), check.Equals, "30m")
+
+       content = []byte(fmtKeepBalanceConfig(`"KeepServiceTypes":["disk"],`))
+       _, err = testLoadLegacyConfig(content, f, c)
+       c.Check(err, check.IsNil)
+
+       content = []byte(fmtKeepBalanceConfig(`"KeepServiceTypes":[],`))
+       _, err = testLoadLegacyConfig(content, f, c)
+       c.Check(err, check.IsNil)
+
+       content = []byte(fmtKeepBalanceConfig(`"KeepServiceTypes":["proxy"],`))
+       _, err = testLoadLegacyConfig(content, f, c)
+       c.Check(err, check.NotNil)
+
+       content = []byte(fmtKeepBalanceConfig(`"KeepServiceTypes":["disk", "proxy"],`))
+       _, err = testLoadLegacyConfig(content, f, c)
+       c.Check(err, check.NotNil)
+
+       content = []byte(fmtKeepBalanceConfig(`"KeepServiceList":{},`))
+       _, err = testLoadLegacyConfig(content, f, c)
+       c.Check(err, check.NotNil)
+}
+
+func fmtKeepBalanceConfig(param string) string {
+       return fmt.Sprintf(`
+{
+       "Client": {
+               "Scheme": "",
+               "APIHost": "example.com",
+               "AuthToken": "abcdefg",
+               "Insecure": false
+       },
+       "Listen": ":80",
+       %s
+       "RunPeriod": "10m",
+       "CollectionBatchSize": 100000,
+       "CollectionBuffers": 1000,
+       "RequestTimeout": "30m",
+       "ManagementToken": "xyzzy",
+       "LostBlocksFile": "testfile"
+}
+`, param)
+}
index 8df561c00fa0fce082ee9635f7f3fbf1ef067936..5437836f6fee05f3aded39954ea8d626d3c12f6e 100644 (file)
@@ -99,6 +99,10 @@ var whitelist = map[string]bool{
        "Collections.TrashSweepInterval":               false,
        "Collections.TrustAllContent":                  false,
        "Collections.WebDAVCache":                      false,
+       "Collections.BalanceCollectionBatch":           false,
+       "Collections.BalancePeriod":                    false,
+       "Collections.BlobMissingReport":                false,
+       "Collections.BalanceCollectionBuffers":         false,
        "Containers":                                   true,
        "Containers.CloudVMs":                          false,
        "Containers.CrunchRunCommand":                  false,
index d18251b27f59e4e23a0f033dd098ed082a0c5e74..d21bb2d284b57d5dfff01a346c0708d98ca8db13 100644 (file)
@@ -374,13 +374,26 @@ Clusters:
       # collection's replication_desired attribute is nil.
       DefaultReplication: 2
 
-      # Lifetime (in seconds) of blob permission signatures generated by
-      # the API server. This determines how long a client can take (after
-      # retrieving a collection record) to retrieve the collection data
-      # from Keep. If the client needs more time than that (assuming the
-      # collection still has the same content and the relevant user/token
-      # still has permission) the client can retrieve the collection again
-      # to get fresh signatures.
+      # BlobSigningTTL determines the minimum lifetime of transient
+      # data, i.e., blocks that are not referenced by
+      # collections. Unreferenced blocks exist for two reasons:
+      #
+      # 1) A data block must be written to a disk/cloud backend device
+      # before a collection can be created/updated with a reference to
+      # it.
+      #
+      # 2) Deleting or updating a collection can remove the last
+      # remaining reference to a data block.
+      #
+      # If BlobSigningTTL is too short, long-running
+      # processes/containers will fail when they take too long (a)
+      # between writing blocks and writing collections that reference
+      # them, or (b) between reading collections and reading the
+      # referenced blocks.
+      #
+      # If BlobSigningTTL is too long, data will still be stored long
+      # after the referring collections are deleted, and you will
+      # needlessly fill up disks or waste money on cloud storage.
       #
       # Modifying BlobSigningTTL invalidates existing signatures; see
       # BlobSigningKey note above.
@@ -388,6 +401,36 @@ Clusters:
       # The default is 2 weeks.
       BlobSigningTTL: 336h
 
+      # When running keep-balance, this is the destination filename for
+      # the list of lost block hashes if there are any, one per line.
+      # Updated automically during each successful run.
+      BlobMissingReport: ""
+
+      # keep-balance operates periodically, i.e.: do a
+      # scan/balance operation, sleep, repeat.
+      #
+      # BalancePeriod determines the interval between start times of
+      # successive scan/balance operations. If a scan/balance operation
+      # takes longer than RunPeriod, the next one will follow it
+      # immediately.
+      #
+      # If SIGUSR1 is received during an idle period between operations,
+      # the next operation will start immediately.
+      BalancePeriod: 10m
+
+      # Limits the number of collections retrieved by keep-balance per
+      # API transaction. If this is zero, page size is
+      # determined by the API server's own page size limits (see
+      # API.MaxItemsPerResponse and API.MaxIndexDatabaseRead).
+      BalanceCollectionBatch: 0
+
+      # The size of keep-balance's internal queue of
+      # collections. Higher values use more memory and improve throughput
+      # by allowing keep-balance to fetch the next page of collections
+      # while the current page is still being processed. If this is zero
+      # or omitted, pages are processed serially.
+      BalanceCollectionBuffers: 1000
+
       # Default lifetime for ephemeral collections: 2 weeks. This must not
       # be less than BlobSigningTTL.
       DefaultTrashLifetime: 336h
index 61d80a3c58f535733ad65b9053b55283c9d08510..21d17227372d4d3f6776b2526581508d89c937ef 100644 (file)
@@ -37,6 +37,7 @@ type Loader struct {
        WebsocketPath           string
        KeepproxyPath           string
        GitHttpdPath            string
+       KeepBalancePath         string
 
        configdata []byte
 }
@@ -69,6 +70,7 @@ func (ldr *Loader) SetupFlags(flagset *flag.FlagSet) {
        flagset.StringVar(&ldr.WebsocketPath, "legacy-ws-config", defaultWebsocketConfigPath, "Legacy arvados-ws configuration `file`")
        flagset.StringVar(&ldr.KeepproxyPath, "legacy-keepproxy-config", defaultKeepproxyConfigPath, "Legacy keepproxy configuration `file`")
        flagset.StringVar(&ldr.GitHttpdPath, "legacy-git-httpd-config", defaultGitHttpdConfigPath, "Legacy arv-git-httpd configuration `file`")
+       flagset.StringVar(&ldr.KeepBalancePath, "legacy-keepbalance-config", defaultKeepBalanceConfigPath, "Legacy keep-balance configuration `file`")
        flagset.BoolVar(&ldr.SkipLegacy, "skip-legacy", false, "Don't load legacy config files")
 }
 
@@ -149,6 +151,9 @@ func (ldr *Loader) MungeLegacyConfigArgs(lgr logrus.FieldLogger, args []string,
        if legacyConfigArg != "-legacy-git-httpd-config" {
                ldr.GitHttpdPath = ""
        }
+       if legacyConfigArg != "-legacy-keepbalance-config" {
+               ldr.KeepBalancePath = ""
+       }
 
        return munged
 }
@@ -252,6 +257,7 @@ func (ldr *Loader) Load() (*arvados.Config, error) {
                        ldr.loadOldWebsocketConfig(&cfg),
                        ldr.loadOldKeepproxyConfig(&cfg),
                        ldr.loadOldGitHttpdConfig(&cfg),
+                       ldr.loadOldKeepBalanceConfig(&cfg),
                } {
                        if err != nil {
                                return nil, err
index 076a3c44d7701c63e691d1ff54a1bae4be8e6dab..7c1c3538094869ff82a510226575a2dbbd0491ab 100644 (file)
@@ -119,6 +119,11 @@ type Cluster struct {
                TrashSweepInterval    Duration
                TrustAllContent       bool
 
+               BlobMissingReport        string
+               BalancePeriod            Duration
+               BalanceCollectionBatch   int
+               BalanceCollectionBuffers int
+
                WebDAVCache WebDAVCacheConfig
        }
        Git struct {
index 9f814a20d3572b58ea5c65333a94a6d0136f62c2..e50b0b505aee471f30918772f66f18ec0838e31d 100644 (file)
@@ -66,7 +66,7 @@ type Balancer struct {
 // Typical usage:
 //
 //   runOptions, err = (&Balancer{}).Run(config, runOptions)
-func (bal *Balancer) Run(config Config, runOptions RunOptions) (nextRunOptions RunOptions, err error) {
+func (bal *Balancer) Run(client *arvados.Client, cluster *arvados.Cluster, runOptions RunOptions) (nextRunOptions RunOptions, err error) {
        nextRunOptions = runOptions
 
        defer bal.time("sweep", "wall clock time to run one full sweep")()
@@ -95,24 +95,20 @@ func (bal *Balancer) Run(config Config, runOptions RunOptions) (nextRunOptions R
                bal.lostBlocks = ioutil.Discard
        }
 
-       if len(config.KeepServiceList.Items) > 0 {
-               err = bal.SetKeepServices(config.KeepServiceList)
-       } else {
-               err = bal.DiscoverKeepServices(&config.Client, config.KeepServiceTypes)
-       }
+       err = bal.DiscoverKeepServices(client)
        if err != nil {
                return
        }
 
        for _, srv := range bal.KeepServices {
-               err = srv.discoverMounts(&config.Client)
+               err = srv.discoverMounts(client)
                if err != nil {
                        return
                }
        }
        bal.cleanupMounts()
 
-       if err = bal.CheckSanityEarly(&config.Client); err != nil {
+       if err = bal.CheckSanityEarly(client); err != nil {
                return
        }
        rs := bal.rendezvousState()
@@ -121,7 +117,7 @@ func (bal *Balancer) Run(config Config, runOptions RunOptions) (nextRunOptions R
                        bal.logf("notice: KeepServices list has changed since last run")
                }
                bal.logf("clearing existing trash lists, in case the new rendezvous order differs from previous run")
-               if err = bal.ClearTrashLists(&config.Client); err != nil {
+               if err = bal.ClearTrashLists(client); err != nil {
                        return
                }
                // The current rendezvous state becomes "safe" (i.e.,
@@ -130,7 +126,7 @@ func (bal *Balancer) Run(config Config, runOptions RunOptions) (nextRunOptions R
                // succeed in clearing existing trash lists.
                nextRunOptions.SafeRendezvousState = rs
        }
-       if err = bal.GetCurrentState(&config.Client, config.CollectionBatchSize, config.CollectionBuffers); err != nil {
+       if err = bal.GetCurrentState(client, cluster.Collections.BalanceCollectionBatch, cluster.Collections.BalanceCollectionBuffers); err != nil {
                return
        }
        bal.ComputeChangeSets()
@@ -150,14 +146,14 @@ func (bal *Balancer) Run(config Config, runOptions RunOptions) (nextRunOptions R
                lbFile = nil
        }
        if runOptions.CommitPulls {
-               err = bal.CommitPulls(&config.Client)
+               err = bal.CommitPulls(client)
                if err != nil {
                        // Skip trash if we can't pull. (Too cautious?)
                        return
                }
        }
        if runOptions.CommitTrash {
-               err = bal.CommitTrash(&config.Client)
+               err = bal.CommitTrash(client)
        }
        return
 }
@@ -176,15 +172,11 @@ func (bal *Balancer) SetKeepServices(srvList arvados.KeepServiceList) error {
 
 // DiscoverKeepServices sets the list of KeepServices by calling the
 // API to get a list of all services, and selecting the ones whose
-// ServiceType is in okTypes.
-func (bal *Balancer) DiscoverKeepServices(c *arvados.Client, okTypes []string) error {
+// ServiceType is "disk"
+func (bal *Balancer) DiscoverKeepServices(c *arvados.Client) error {
        bal.KeepServices = make(map[string]*KeepService)
-       ok := make(map[string]bool)
-       for _, t := range okTypes {
-               ok[t] = true
-       }
        return c.EachKeepService(func(srv arvados.KeepService) error {
-               if ok[srv.ServiceType] {
+               if srv.ServiceType == "disk" {
                        bal.KeepServices[srv.UUID] = &KeepService{
                                KeepService: srv,
                                ChangeSet:   &ChangeSet{},
@@ -449,7 +441,7 @@ func (bal *Balancer) addCollection(coll arvados.Collection) error {
        if coll.ReplicationDesired != nil {
                repl = *coll.ReplicationDesired
        }
-       debugf("%v: %d block x%d", coll.UUID, len(blkids), repl)
+       bal.Logger.Debugf("%v: %d block x%d", coll.UUID, len(blkids), repl)
        // Pass pdh to IncreaseDesired only if LostBlocksFile is being
        // written -- otherwise it's just a waste of memory.
        pdh := ""
@@ -566,7 +558,7 @@ type balanceResult struct {
 // balanceBlock compares current state to desired state for a single
 // block, and makes the appropriate ChangeSet calls.
 func (bal *Balancer) balanceBlock(blkid arvados.SizedDigest, blk *BlockState) balanceResult {
-       debugf("balanceBlock: %v %+v", blkid, blk)
+       bal.Logger.Debugf("balanceBlock: %v %+v", blkid, blk)
 
        type slot struct {
                mnt  *KeepMount // never nil
index db530bc4926de88502132730f35c816ec3cf92b6..a3abc9f96a466ea00dab1047e3b9254d93fcebf9 100644 (file)
@@ -5,6 +5,7 @@
 package main
 
 import (
+       "bytes"
        "encoding/json"
        "fmt"
        "io"
@@ -16,8 +17,12 @@ import (
        "sync"
        "time"
 
+       "git.curoverse.com/arvados.git/lib/config"
        "git.curoverse.com/arvados.git/sdk/go/arvados"
-       "github.com/sirupsen/logrus"
+       "git.curoverse.com/arvados.git/sdk/go/arvadostest"
+       "git.curoverse.com/arvados.git/sdk/go/ctxlog"
+       "github.com/prometheus/client_golang/prometheus"
+       "github.com/prometheus/common/expfmt"
        check "gopkg.in/check.v1"
 )
 
@@ -303,41 +308,36 @@ func (s *stubServer) serveKeepstorePull() *reqTracker {
 
 type runSuite struct {
        stub   stubServer
-       config Config
-}
-
-// make a log.Logger that writes to the current test's c.Log().
-func (s *runSuite) logger(c *check.C) *logrus.Logger {
-       r, w := io.Pipe()
-       go func() {
-               buf := make([]byte, 10000)
-               for {
-                       n, err := r.Read(buf)
-                       if n > 0 {
-                               if buf[n-1] == '\n' {
-                                       n--
-                               }
-                               c.Log(string(buf[:n]))
-                       }
-                       if err != nil {
-                               break
-                       }
-               }
-       }()
-       logger := logrus.New()
-       logger.Out = w
-       return logger
+       config *arvados.Cluster
+       client *arvados.Client
+}
+
+func (s *runSuite) newServer(options *RunOptions) *Server {
+       srv := &Server{
+               Cluster:    s.config,
+               ArvClient:  s.client,
+               RunOptions: *options,
+               Metrics:    newMetrics(prometheus.NewRegistry()),
+               Logger:     options.Logger,
+               Dumper:     options.Dumper,
+       }
+       return srv
 }
 
 func (s *runSuite) SetUpTest(c *check.C) {
-       s.config = Config{
-               Client: arvados.Client{
-                       AuthToken: "xyzzy",
-                       APIHost:   "zzzzz.arvadosapi.com",
-                       Client:    s.stub.Start()},
-               KeepServiceTypes: []string{"disk"},
-               RunPeriod:        arvados.Duration(time.Second),
-       }
+       cfg, err := config.NewLoader(nil, ctxlog.TestLogger(c)).Load()
+       c.Assert(err, check.Equals, nil)
+       s.config, err = cfg.GetCluster("")
+       c.Assert(err, check.Equals, nil)
+
+       s.config.Collections.BalancePeriod = arvados.Duration(time.Second)
+       arvadostest.SetServiceURL(&s.config.Services.Keepbalance, "http://localhost:/")
+
+       s.client = &arvados.Client{
+               AuthToken: "xyzzy",
+               APIHost:   "zzzzz.arvadosapi.com",
+               Client:    s.stub.Start()}
+
        s.stub.serveDiscoveryDoc()
        s.stub.logf = c.Logf
 }
@@ -350,7 +350,7 @@ func (s *runSuite) TestRefuseZeroCollections(c *check.C) {
        opts := RunOptions{
                CommitPulls: true,
                CommitTrash: true,
-               Logger:      s.logger(c),
+               Logger:      ctxlog.TestLogger(c),
        }
        s.stub.serveCurrentUserAdmin()
        s.stub.serveZeroCollections()
@@ -359,40 +359,18 @@ func (s *runSuite) TestRefuseZeroCollections(c *check.C) {
        s.stub.serveKeepstoreIndexFoo4Bar1()
        trashReqs := s.stub.serveKeepstoreTrash()
        pullReqs := s.stub.serveKeepstorePull()
-       srv, err := NewServer(s.config, opts)
-       c.Assert(err, check.IsNil)
-       _, err = srv.Run()
+       srv := s.newServer(&opts)
+       _, err := srv.runOnce()
        c.Check(err, check.ErrorMatches, "received zero collections")
        c.Check(trashReqs.Count(), check.Equals, 4)
        c.Check(pullReqs.Count(), check.Equals, 0)
 }
 
-func (s *runSuite) TestServiceTypes(c *check.C) {
-       opts := RunOptions{
-               CommitPulls: true,
-               CommitTrash: true,
-               Logger:      s.logger(c),
-       }
-       s.config.KeepServiceTypes = []string{"unlisted-type"}
-       s.stub.serveCurrentUserAdmin()
-       s.stub.serveFooBarFileCollections()
-       s.stub.serveKeepServices(stubServices)
-       s.stub.serveKeepstoreMounts()
-       indexReqs := s.stub.serveKeepstoreIndexFoo4Bar1()
-       trashReqs := s.stub.serveKeepstoreTrash()
-       srv, err := NewServer(s.config, opts)
-       c.Assert(err, check.IsNil)
-       _, err = srv.Run()
-       c.Check(err, check.IsNil)
-       c.Check(indexReqs.Count(), check.Equals, 0)
-       c.Check(trashReqs.Count(), check.Equals, 0)
-}
-
 func (s *runSuite) TestRefuseNonAdmin(c *check.C) {
        opts := RunOptions{
                CommitPulls: true,
                CommitTrash: true,
-               Logger:      s.logger(c),
+               Logger:      ctxlog.TestLogger(c),
        }
        s.stub.serveCurrentUserNotAdmin()
        s.stub.serveZeroCollections()
@@ -400,9 +378,8 @@ func (s *runSuite) TestRefuseNonAdmin(c *check.C) {
        s.stub.serveKeepstoreMounts()
        trashReqs := s.stub.serveKeepstoreTrash()
        pullReqs := s.stub.serveKeepstorePull()
-       srv, err := NewServer(s.config, opts)
-       c.Assert(err, check.IsNil)
-       _, err = srv.Run()
+       srv := s.newServer(&opts)
+       _, err := srv.runOnce()
        c.Check(err, check.ErrorMatches, "current user .* is not .* admin user")
        c.Check(trashReqs.Count(), check.Equals, 0)
        c.Check(pullReqs.Count(), check.Equals, 0)
@@ -412,7 +389,7 @@ func (s *runSuite) TestDetectSkippedCollections(c *check.C) {
        opts := RunOptions{
                CommitPulls: true,
                CommitTrash: true,
-               Logger:      s.logger(c),
+               Logger:      ctxlog.TestLogger(c),
        }
        s.stub.serveCurrentUserAdmin()
        s.stub.serveCollectionsButSkipOne()
@@ -421,9 +398,8 @@ func (s *runSuite) TestDetectSkippedCollections(c *check.C) {
        s.stub.serveKeepstoreIndexFoo4Bar1()
        trashReqs := s.stub.serveKeepstoreTrash()
        pullReqs := s.stub.serveKeepstorePull()
-       srv, err := NewServer(s.config, opts)
-       c.Assert(err, check.IsNil)
-       _, err = srv.Run()
+       srv := s.newServer(&opts)
+       _, err := srv.runOnce()
        c.Check(err, check.ErrorMatches, `Retrieved 2 collections with modtime <= .* but server now reports there are 3 collections.*`)
        c.Check(trashReqs.Count(), check.Equals, 4)
        c.Check(pullReqs.Count(), check.Equals, 0)
@@ -432,12 +408,12 @@ func (s *runSuite) TestDetectSkippedCollections(c *check.C) {
 func (s *runSuite) TestWriteLostBlocks(c *check.C) {
        lostf, err := ioutil.TempFile("", "keep-balance-lost-blocks-test-")
        c.Assert(err, check.IsNil)
-       s.config.LostBlocksFile = lostf.Name()
+       s.config.Collections.BlobMissingReport = lostf.Name()
        defer os.Remove(lostf.Name())
        opts := RunOptions{
                CommitPulls: true,
                CommitTrash: true,
-               Logger:      s.logger(c),
+               Logger:      ctxlog.TestLogger(c),
        }
        s.stub.serveCurrentUserAdmin()
        s.stub.serveFooBarFileCollections()
@@ -446,9 +422,9 @@ func (s *runSuite) TestWriteLostBlocks(c *check.C) {
        s.stub.serveKeepstoreIndexFoo1()
        s.stub.serveKeepstoreTrash()
        s.stub.serveKeepstorePull()
-       srv, err := NewServer(s.config, opts)
+       srv := s.newServer(&opts)
        c.Assert(err, check.IsNil)
-       _, err = srv.Run()
+       _, err = srv.runOnce()
        c.Check(err, check.IsNil)
        lost, err := ioutil.ReadFile(lostf.Name())
        c.Assert(err, check.IsNil)
@@ -459,7 +435,7 @@ func (s *runSuite) TestDryRun(c *check.C) {
        opts := RunOptions{
                CommitPulls: false,
                CommitTrash: false,
-               Logger:      s.logger(c),
+               Logger:      ctxlog.TestLogger(c),
        }
        s.stub.serveCurrentUserAdmin()
        collReqs := s.stub.serveFooBarFileCollections()
@@ -468,9 +444,8 @@ func (s *runSuite) TestDryRun(c *check.C) {
        s.stub.serveKeepstoreIndexFoo4Bar1()
        trashReqs := s.stub.serveKeepstoreTrash()
        pullReqs := s.stub.serveKeepstorePull()
-       srv, err := NewServer(s.config, opts)
-       c.Assert(err, check.IsNil)
-       bal, err := srv.Run()
+       srv := s.newServer(&opts)
+       bal, err := srv.runOnce()
        c.Check(err, check.IsNil)
        for _, req := range collReqs.reqs {
                c.Check(req.Form.Get("include_trash"), check.Equals, "true")
@@ -486,16 +461,15 @@ func (s *runSuite) TestDryRun(c *check.C) {
 func (s *runSuite) TestCommit(c *check.C) {
        lostf, err := ioutil.TempFile("", "keep-balance-lost-blocks-test-")
        c.Assert(err, check.IsNil)
-       s.config.LostBlocksFile = lostf.Name()
+       s.config.Collections.BlobMissingReport = lostf.Name()
        defer os.Remove(lostf.Name())
 
-       s.config.Listen = ":"
        s.config.ManagementToken = "xyzzy"
        opts := RunOptions{
                CommitPulls: true,
                CommitTrash: true,
-               Logger:      s.logger(c),
-               Dumper:      s.logger(c),
+               Logger:      ctxlog.TestLogger(c),
+               Dumper:      ctxlog.TestLogger(c),
        }
        s.stub.serveCurrentUserAdmin()
        s.stub.serveFooBarFileCollections()
@@ -504,9 +478,8 @@ func (s *runSuite) TestCommit(c *check.C) {
        s.stub.serveKeepstoreIndexFoo4Bar1()
        trashReqs := s.stub.serveKeepstoreTrash()
        pullReqs := s.stub.serveKeepstorePull()
-       srv, err := NewServer(s.config, opts)
-       c.Assert(err, check.IsNil)
-       bal, err := srv.Run()
+       srv := s.newServer(&opts)
+       bal, err := srv.runOnce()
        c.Check(err, check.IsNil)
        c.Check(trashReqs.Count(), check.Equals, 8)
        c.Check(pullReqs.Count(), check.Equals, 4)
@@ -520,22 +493,22 @@ func (s *runSuite) TestCommit(c *check.C) {
        c.Assert(err, check.IsNil)
        c.Check(string(lost), check.Equals, "")
 
-       metrics := s.getMetrics(c, srv)
-       c.Check(metrics, check.Matches, `(?ms).*\narvados_keep_total_bytes 15\n.*`)
-       c.Check(metrics, check.Matches, `(?ms).*\narvados_keepbalance_changeset_compute_seconds_sum [0-9\.]+\n.*`)
-       c.Check(metrics, check.Matches, `(?ms).*\narvados_keepbalance_changeset_compute_seconds_count 1\n.*`)
-       c.Check(metrics, check.Matches, `(?ms).*\narvados_keep_dedup_byte_ratio 1\.5\n.*`)
-       c.Check(metrics, check.Matches, `(?ms).*\narvados_keep_dedup_block_ratio 1\.5\n.*`)
+       buf, err := s.getMetrics(c, srv)
+       c.Check(err, check.IsNil)
+       c.Check(buf, check.Matches, `(?ms).*\narvados_keep_total_bytes 15\n.*`)
+       c.Check(buf, check.Matches, `(?ms).*\narvados_keepbalance_changeset_compute_seconds_sum [0-9\.]+\n.*`)
+       c.Check(buf, check.Matches, `(?ms).*\narvados_keepbalance_changeset_compute_seconds_count 1\n.*`)
+       c.Check(buf, check.Matches, `(?ms).*\narvados_keep_dedup_byte_ratio 1\.5\n.*`)
+       c.Check(buf, check.Matches, `(?ms).*\narvados_keep_dedup_block_ratio 1\.5\n.*`)
 }
 
 func (s *runSuite) TestRunForever(c *check.C) {
-       s.config.Listen = ":"
        s.config.ManagementToken = "xyzzy"
        opts := RunOptions{
                CommitPulls: true,
                CommitTrash: true,
-               Logger:      s.logger(c),
-               Dumper:      s.logger(c),
+               Logger:      ctxlog.TestLogger(c),
+               Dumper:      ctxlog.TestLogger(c),
        }
        s.stub.serveCurrentUserAdmin()
        s.stub.serveFooBarFileCollections()
@@ -546,13 +519,12 @@ func (s *runSuite) TestRunForever(c *check.C) {
        pullReqs := s.stub.serveKeepstorePull()
 
        stop := make(chan interface{})
-       s.config.RunPeriod = arvados.Duration(time.Millisecond)
-       srv, err := NewServer(s.config, opts)
-       c.Assert(err, check.IsNil)
+       s.config.Collections.BalancePeriod = arvados.Duration(time.Millisecond)
+       srv := s.newServer(&opts)
 
        done := make(chan bool)
        go func() {
-               srv.RunForever(stop)
+               srv.runForever(stop)
                close(done)
        }()
 
@@ -567,18 +539,24 @@ func (s *runSuite) TestRunForever(c *check.C) {
        <-done
        c.Check(pullReqs.Count() >= 16, check.Equals, true)
        c.Check(trashReqs.Count(), check.Equals, pullReqs.Count()+4)
-       c.Check(s.getMetrics(c, srv), check.Matches, `(?ms).*\narvados_keepbalance_changeset_compute_seconds_count `+fmt.Sprintf("%d", pullReqs.Count()/4)+`\n.*`)
+
+       buf, err := s.getMetrics(c, srv)
+       c.Check(err, check.IsNil)
+       c.Check(buf, check.Matches, `(?ms).*\narvados_keepbalance_changeset_compute_seconds_count `+fmt.Sprintf("%d", pullReqs.Count()/4)+`\n.*`)
 }
 
-func (s *runSuite) getMetrics(c *check.C, srv *Server) string {
-       resp, err := http.Get("http://" + srv.listening + "/metrics")
-       c.Assert(err, check.IsNil)
-       c.Check(resp.StatusCode, check.Equals, http.StatusUnauthorized)
+func (s *runSuite) getMetrics(c *check.C, srv *Server) (*bytes.Buffer, error) {
+       mfs, err := srv.Metrics.reg.Gather()
+       if err != nil {
+               return nil, err
+       }
 
-       resp, err = http.Get("http://" + srv.listening + "/metrics?api_token=xyzzy")
-       c.Assert(err, check.IsNil)
-       c.Check(resp.StatusCode, check.Equals, http.StatusOK)
-       buf, err := ioutil.ReadAll(resp.Body)
-       c.Check(err, check.IsNil)
-       return string(buf)
+       var buf bytes.Buffer
+       for _, mf := range mfs {
+               if _, err := expfmt.MetricFamilyToText(&buf, mf); err != nil {
+                       return nil, err
+               }
+       }
+
+       return &buf, nil
 }
index e372d37841a7b095cc659216bb11b87b7bc793dd..6cffa8ded4dbad6975225949e871852e5ca2d50e 100644 (file)
@@ -13,6 +13,7 @@ import (
        "time"
 
        "git.curoverse.com/arvados.git/sdk/go/arvados"
+       "git.curoverse.com/arvados.git/sdk/go/ctxlog"
        check "gopkg.in/check.v1"
 )
 
@@ -69,6 +70,7 @@ func (bal *balancerSuite) SetUpSuite(c *check.C) {
        }
 
        bal.signatureTTL = 3600
+       bal.Logger = ctxlog.TestLogger(c)
 }
 
 func (bal *balancerSuite) SetUpTest(c *check.C) {
index 6aaf07abae395241fdbd5f26be8ae111f14aac1f..a2200e1db90a4ddf69fd65112c432df9bbcba2c6 100644 (file)
@@ -29,7 +29,7 @@ func (s *integrationSuite) TestIdenticalTimestamps(c *check.C) {
                        longestStreak := 0
                        var lastMod time.Time
                        sawUUID := make(map[string]bool)
-                       err := EachCollection(&s.config.Client, pageSize, func(c arvados.Collection) error {
+                       err := EachCollection(s.client, pageSize, func(c arvados.Collection) error {
                                if c.ModifiedAt == nil {
                                        return nil
                                }
index a79779c7dc8f9fdb5eb7316a74c28fb614d9da52..5b0dc123ae49a627a98c4f5254ff6a1649e869e6 100644 (file)
@@ -11,10 +11,13 @@ import (
        "testing"
        "time"
 
+       "git.curoverse.com/arvados.git/lib/config"
        "git.curoverse.com/arvados.git/sdk/go/arvados"
        "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
        "git.curoverse.com/arvados.git/sdk/go/arvadostest"
+       "git.curoverse.com/arvados.git/sdk/go/ctxlog"
        "git.curoverse.com/arvados.git/sdk/go/keepclient"
+       "github.com/prometheus/client_golang/prometheus"
        "github.com/sirupsen/logrus"
        check "gopkg.in/check.v1"
 )
@@ -22,7 +25,8 @@ import (
 var _ = check.Suite(&integrationSuite{})
 
 type integrationSuite struct {
-       config     Config
+       config     *arvados.Cluster
+       client     *arvados.Client
        keepClient *keepclient.KeepClient
 }
 
@@ -59,14 +63,16 @@ func (s *integrationSuite) TearDownSuite(c *check.C) {
 }
 
 func (s *integrationSuite) SetUpTest(c *check.C) {
-       s.config = Config{
-               Client: arvados.Client{
-                       APIHost:   os.Getenv("ARVADOS_API_HOST"),
-                       AuthToken: arvadostest.DataManagerToken,
-                       Insecure:  true,
-               },
-               KeepServiceTypes: []string{"disk"},
-               RunPeriod:        arvados.Duration(time.Second),
+       cfg, err := config.NewLoader(nil, ctxlog.TestLogger(c)).Load()
+       c.Assert(err, check.Equals, nil)
+       s.config, err = cfg.GetCluster("")
+       c.Assert(err, check.Equals, nil)
+       s.config.Collections.BalancePeriod = arvados.Duration(time.Second)
+
+       s.client = &arvados.Client{
+               APIHost:   os.Getenv("ARVADOS_API_HOST"),
+               AuthToken: arvadostest.DataManagerToken,
+               Insecure:  true,
        }
 }
 
@@ -84,9 +90,9 @@ func (s *integrationSuite) TestBalanceAPIFixtures(c *check.C) {
 
                bal := &Balancer{
                        Logger:  logger,
-                       Metrics: newMetrics(),
+                       Metrics: newMetrics(prometheus.NewRegistry()),
                }
-               nextOpts, err := bal.Run(s.config, opts)
+               nextOpts, err := bal.Run(s.client, s.config, opts)
                c.Check(err, check.IsNil)
                c.Check(nextOpts.SafeRendezvousState, check.Not(check.Equals), "")
                c.Check(nextOpts.CommitPulls, check.Equals, true)
index 563871607874f9ad44a07315ce08bfd68274a23b..1b71fb4e44350bac913961e598494d0c01a333ab 100644 (file)
@@ -6,7 +6,6 @@
 Description=Arvados Keep Balance
 Documentation=https://doc.arvados.org/
 After=network.target
-AssertPathExists=/etc/arvados/keep-balance/keep-balance.yml
 
 # systemd==229 (ubuntu:xenial) obeys StartLimitInterval in the [Unit] section
 StartLimitInterval=0
index 84516a821060da1b795da1b40655a9a62157fd52..cf844ab050043c1662c3cf5cd62ef9ef238a6789 100644 (file)
@@ -5,97 +5,85 @@
 package main
 
 import (
-       "encoding/json"
+       "context"
        "flag"
        "fmt"
-       "log"
-       "net/http"
+       "io"
        "os"
-       "time"
 
+       "git.curoverse.com/arvados.git/lib/config"
+       "git.curoverse.com/arvados.git/lib/service"
        "git.curoverse.com/arvados.git/sdk/go/arvados"
-       "git.curoverse.com/arvados.git/sdk/go/config"
+       "git.curoverse.com/arvados.git/sdk/go/ctxlog"
+       "github.com/prometheus/client_golang/prometheus"
        "github.com/sirupsen/logrus"
 )
 
-var debugf = func(string, ...interface{}) {}
-
 func main() {
-       var cfg Config
-       var runOptions RunOptions
+       os.Exit(runCommand(os.Args[0], os.Args[1:], os.Stdin, os.Stdout, os.Stderr))
+}
+
+func runCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
+       logger := ctxlog.FromContext(context.Background())
 
-       configPath := flag.String("config", defaultConfigPath,
-               "`path` of JSON or YAML configuration file")
-       serviceListPath := flag.String("config.KeepServiceList", "",
-               "`path` of JSON or YAML file with list of keep services to balance, as given by \"arv keep_service list\" "+
-                       "(default: config[\"KeepServiceList\"], or if none given, get all available services and filter by config[\"KeepServiceTypes\"])")
-       flag.BoolVar(&runOptions.Once, "once", false,
+       var options RunOptions
+       flags := flag.NewFlagSet(prog, flag.ExitOnError)
+       flags.BoolVar(&options.Once, "once", false,
                "balance once and then exit")
-       flag.BoolVar(&runOptions.CommitPulls, "commit-pulls", false,
+       flags.BoolVar(&options.CommitPulls, "commit-pulls", false,
                "send pull requests (make more replicas of blocks that are underreplicated or are not in optimal rendezvous probe order)")
-       flag.BoolVar(&runOptions.CommitTrash, "commit-trash", false,
+       flags.BoolVar(&options.CommitTrash, "commit-trash", false,
                "send trash requests (delete unreferenced old blocks, and excess replicas of overreplicated blocks)")
-       dumpConfig := flag.Bool("dump-config", false, "write current configuration to stdout and exit")
-       dumpFlag := flag.Bool("dump", false, "dump details for each block to stdout")
-       debugFlag := flag.Bool("debug", false, "enable debug messages")
-       getVersion := flag.Bool("version", false, "Print version information and exit.")
-       flag.Usage = usage
-       flag.Parse()
+       flags.Bool("version", false, "Write version information to stdout and exit 0")
+       dumpFlag := flags.Bool("dump", false, "dump details for each block to stdout")
 
-       // Print version information if requested
-       if *getVersion {
-               fmt.Printf("keep-balance %s\n", version)
-               return
-       }
+       loader := config.NewLoader(os.Stdin, logger)
+       loader.SetupFlags(flags)
 
-       mustReadConfig(&cfg, *configPath)
-       if *serviceListPath != "" {
-               mustReadConfig(&cfg.KeepServiceList, *serviceListPath)
-       }
+       munged := loader.MungeLegacyConfigArgs(logger, args, "-legacy-keepbalance-config")
+       flags.Parse(munged)
 
-       if *dumpConfig {
-               log.Fatal(config.DumpAndExit(cfg))
-       }
-
-       to := time.Duration(cfg.RequestTimeout)
-       if to == 0 {
-               to = 30 * time.Minute
-       }
-       arvados.DefaultSecureClient.Timeout = to
-       arvados.InsecureHTTPClient.Timeout = to
-       http.DefaultClient.Timeout = to
-
-       log.Printf("keep-balance %s started", version)
-
-       if *debugFlag {
-               debugf = log.Printf
-               if j, err := json.Marshal(cfg); err != nil {
-                       log.Fatal(err)
-               } else {
-                       log.Printf("config is %s", j)
-               }
-       }
        if *dumpFlag {
                dumper := logrus.New()
                dumper.Out = os.Stdout
                dumper.Formatter = &logrus.TextFormatter{}
-               runOptions.Dumper = dumper
-       }
-       srv, err := NewServer(cfg, runOptions)
-       if err != nil {
-               // (don't run)
-       } else if runOptions.Once {
-               _, err = srv.Run()
-       } else {
-               err = srv.RunForever(nil)
-       }
-       if err != nil {
-               log.Fatal(err)
+               options.Dumper = dumper
        }
-}
 
-func mustReadConfig(dst interface{}, path string) {
-       if err := config.LoadFile(dst, path); err != nil {
-               log.Fatal(err)
-       }
+       // Only pass along the version flag, which gets handled in RunCommand
+       args = nil
+       flags.Visit(func(f *flag.Flag) {
+               if f.Name == "version" {
+                       args = append(args, "-"+f.Name, f.Value.String())
+               }
+       })
+
+       return service.Command(arvados.ServiceNameKeepbalance,
+               func(ctx context.Context, cluster *arvados.Cluster, token string, registry *prometheus.Registry) service.Handler {
+                       if !options.Once && cluster.Collections.BalancePeriod == arvados.Duration(0) {
+                               return service.ErrorHandler(ctx, cluster, fmt.Errorf("cannot start service: Collections.BalancePeriod is zero (if you want to run once and then exit, use the -once flag)"))
+                       }
+
+                       ac, err := arvados.NewClientFromConfig(cluster)
+                       ac.AuthToken = token
+                       if err != nil {
+                               return service.ErrorHandler(ctx, cluster, fmt.Errorf("error initializing client from cluster config: %s", err))
+                       }
+
+                       if options.Logger == nil {
+                               options.Logger = ctxlog.FromContext(ctx)
+                       }
+
+                       srv := &Server{
+                               Cluster:    cluster,
+                               ArvClient:  ac,
+                               RunOptions: options,
+                               Metrics:    newMetrics(registry),
+                               Logger:     options.Logger,
+                               Dumper:     options.Dumper,
+                       }
+
+                       go srv.run()
+                       return srv
+               }).RunCommand(prog, args, stdin, stdout, stderr)
 }
diff --git a/services/keep-balance/main_test.go b/services/keep-balance/main_test.go
deleted file mode 100644 (file)
index a280434..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-package main
-
-import (
-       "time"
-
-       "github.com/ghodss/yaml"
-       check "gopkg.in/check.v1"
-)
-
-var _ = check.Suite(&mainSuite{})
-
-type mainSuite struct{}
-
-func (s *mainSuite) TestExampleJSON(c *check.C) {
-       var config Config
-       c.Check(yaml.Unmarshal(exampleConfigFile, &config), check.IsNil)
-       c.Check(config.KeepServiceTypes, check.DeepEquals, []string{"disk"})
-       c.Check(config.Client.AuthToken, check.Equals, "xyzzy")
-       c.Check(time.Duration(config.RunPeriod), check.Equals, 600*time.Second)
-}
-
-func (s *mainSuite) TestConfigJSONWithKeepServiceList(c *check.C) {
-       var config Config
-       c.Check(yaml.Unmarshal([]byte(`{
-                   "Client": {
-                       "APIHost": "zzzzz.arvadosapi.com:443",
-                       "AuthToken": "xyzzy",
-                       "Insecure": false
-                   },
-                   "KeepServiceList": {
-                       "items": [
-                           {"uuid":"zzzzz-bi64l-abcdefghijklmno", "service_type":"disk", "service_host":"a.zzzzz.arvadosapi.com", "service_port":12345},
-                           {"uuid":"zzzzz-bi64l-bcdefghijklmnop", "service_type":"blob", "service_host":"b.zzzzz.arvadosapi.com", "service_port":12345}
-                       ]
-                   },
-                   "RunPeriod": "600s"
-               }`), &config), check.IsNil)
-       c.Assert(len(config.KeepServiceList.Items), check.Equals, 2)
-       c.Check(config.KeepServiceList.Items[0].UUID, check.Equals, "zzzzz-bi64l-abcdefghijklmno")
-       c.Check(config.KeepServiceList.Items[0].ServicePort, check.Equals, 12345)
-       c.Check(config.Client.AuthToken, check.Equals, "xyzzy")
-}
index 5f3c98723d02a82e9410053657d3262dca3af1be..ce1b1811cc69f28f3fad955a5525b35a666baf3a 100644 (file)
@@ -24,9 +24,9 @@ type metrics struct {
        mtx         sync.Mutex
 }
 
-func newMetrics() *metrics {
+func newMetrics(registry *prometheus.Registry) *metrics {
        return &metrics{
-               reg:         prometheus.NewRegistry(),
+               reg:         registry,
                statsGauges: map[string]setter{},
                observers:   map[string]observer{},
        }
index e2f13a425ed8dfabc729649d98aa7e4ed977899a..b6806d552a89d750d2fbb51a8dce4faa70903b3e 100644 (file)
@@ -5,8 +5,6 @@
 package main
 
 import (
-       "context"
-       "fmt"
        "net/http"
        "os"
        "os/signal"
@@ -14,57 +12,9 @@ import (
        "time"
 
        "git.curoverse.com/arvados.git/sdk/go/arvados"
-       "git.curoverse.com/arvados.git/sdk/go/auth"
-       "git.curoverse.com/arvados.git/sdk/go/ctxlog"
-       "git.curoverse.com/arvados.git/sdk/go/httpserver"
        "github.com/sirupsen/logrus"
 )
 
-var version = "dev"
-
-const (
-       defaultConfigPath = "/etc/arvados/keep-balance/keep-balance.yml"
-       rfc3339NanoFixed  = "2006-01-02T15:04:05.000000000Z07:00"
-)
-
-// Config specifies site configuration, like API credentials and the
-// choice of which servers are to be balanced.
-//
-// Config is loaded from a JSON config file (see usage()).
-type Config struct {
-       // Arvados API endpoint and credentials.
-       Client arvados.Client
-
-       // List of service types (e.g., "disk") to balance.
-       KeepServiceTypes []string
-
-       KeepServiceList arvados.KeepServiceList
-
-       // address, address:port, or :port for management interface
-       Listen string
-
-       // token for management APIs
-       ManagementToken string
-
-       // How often to check
-       RunPeriod arvados.Duration
-
-       // Number of collections to request in each API call
-       CollectionBatchSize int
-
-       // Max collections to buffer in memory (bigger values consume
-       // more memory, but can reduce store-and-forward latency when
-       // fetching pages)
-       CollectionBuffers int
-
-       // Timeout for outgoing http request/response cycle.
-       RequestTimeout arvados.Duration
-
-       // Destination filename for the list of lost block hashes, one
-       // per line. Updated atomically during each successful run.
-       LostBlocksFile string
-}
-
 // RunOptions controls runtime behavior. The flags/options that belong
 // here are the ones that are useful for interactive use. For example,
 // "CommitTrash" is a runtime option rather than a config item because
@@ -87,100 +37,70 @@ type RunOptions struct {
 }
 
 type Server struct {
-       config     Config
-       runOptions RunOptions
-       metrics    *metrics
-       listening  string // for tests
+       http.Handler
+
+       Cluster    *arvados.Cluster
+       ArvClient  *arvados.Client
+       RunOptions RunOptions
+       Metrics    *metrics
 
        Logger logrus.FieldLogger
        Dumper logrus.FieldLogger
 }
 
-// NewServer returns a new Server that runs Balancers using the given
-// config and runOptions.
-func NewServer(config Config, runOptions RunOptions) (*Server, error) {
-       if len(config.KeepServiceList.Items) > 0 && config.KeepServiceTypes != nil {
-               return nil, fmt.Errorf("cannot specify both KeepServiceList and KeepServiceTypes in config")
-       }
-       if !runOptions.Once && config.RunPeriod == arvados.Duration(0) {
-               return nil, fmt.Errorf("you must either use the -once flag, or specify RunPeriod in config")
-       }
-
-       if runOptions.Logger == nil {
-               log := logrus.New()
-               log.Formatter = &logrus.JSONFormatter{
-                       TimestampFormat: rfc3339NanoFixed,
-               }
-               log.Out = os.Stderr
-               runOptions.Logger = log
-       }
-
-       srv := &Server{
-               config:     config,
-               runOptions: runOptions,
-               metrics:    newMetrics(),
-               Logger:     runOptions.Logger,
-               Dumper:     runOptions.Dumper,
-       }
-       return srv, srv.start()
+// CheckHealth implements service.Handler.
+func (srv *Server) CheckHealth() error {
+       return nil
 }
 
-func (srv *Server) start() error {
-       if srv.config.Listen == "" {
-               return nil
-       }
-       ctx := ctxlog.Context(context.Background(), srv.Logger)
-       server := &httpserver.Server{
-               Server: http.Server{
-                       Handler: httpserver.HandlerWithContext(ctx,
-                               httpserver.LogRequests(
-                                       auth.RequireLiteralToken(srv.config.ManagementToken,
-                                               srv.metrics.Handler(srv.Logger)))),
-               },
-               Addr: srv.config.Listen,
+func (srv *Server) run() {
+       var err error
+       if srv.RunOptions.Once {
+               _, err = srv.runOnce()
+       } else {
+               err = srv.runForever(nil)
        }
-       err := server.Start()
        if err != nil {
-               return err
+               srv.Logger.Error(err)
+               os.Exit(1)
+       } else {
+               os.Exit(0)
        }
-       srv.Logger.Printf("listening at %s", server.Addr)
-       srv.listening = server.Addr
-       return nil
 }
 
-func (srv *Server) Run() (*Balancer, error) {
+func (srv *Server) runOnce() (*Balancer, error) {
        bal := &Balancer{
                Logger:         srv.Logger,
                Dumper:         srv.Dumper,
-               Metrics:        srv.metrics,
-               LostBlocksFile: srv.config.LostBlocksFile,
+               Metrics:        srv.Metrics,
+               LostBlocksFile: srv.Cluster.Collections.BlobMissingReport,
        }
        var err error
-       srv.runOptions, err = bal.Run(srv.config, srv.runOptions)
+       srv.RunOptions, err = bal.Run(srv.ArvClient, srv.Cluster, srv.RunOptions)
        return bal, err
 }
 
 // RunForever runs forever, or (for testing purposes) until the given
 // stop channel is ready to receive.
-func (srv *Server) RunForever(stop <-chan interface{}) error {
-       logger := srv.runOptions.Logger
+func (srv *Server) runForever(stop <-chan interface{}) error {
+       logger := srv.Logger
 
-       ticker := time.NewTicker(time.Duration(srv.config.RunPeriod))
+       ticker := time.NewTicker(time.Duration(srv.Cluster.Collections.BalancePeriod))
 
        // The unbuffered channel here means we only hear SIGUSR1 if
        // it arrives while we're waiting in select{}.
        sigUSR1 := make(chan os.Signal)
        signal.Notify(sigUSR1, syscall.SIGUSR1)
 
-       logger.Printf("starting up: will scan every %v and on SIGUSR1", srv.config.RunPeriod)
+       logger.Printf("starting up: will scan every %v and on SIGUSR1", srv.Cluster.Collections.BalancePeriod)
 
        for {
-               if !srv.runOptions.CommitPulls && !srv.runOptions.CommitTrash {
+               if !srv.RunOptions.CommitPulls && !srv.RunOptions.CommitTrash {
                        logger.Print("WARNING: Will scan periodically, but no changes will be committed.")
                        logger.Print("=======  Consider using -commit-pulls and -commit-trash flags.")
                }
 
-               _, err := srv.Run()
+               _, err := srv.runOnce()
                if err != nil {
                        logger.Print("run failed: ", err)
                } else {
@@ -199,7 +119,7 @@ func (srv *Server) RunForever(stop <-chan interface{}) error {
                        // run too soon after the Nth run is triggered
                        // by SIGUSR1.
                        ticker.Stop()
-                       ticker = time.NewTicker(time.Duration(srv.config.RunPeriod))
+                       ticker = time.NewTicker(time.Duration(srv.Cluster.Collections.BalancePeriod))
                }
                logger.Print("starting next run")
        }
diff --git a/services/keep-balance/usage.go b/services/keep-balance/usage.go
deleted file mode 100644 (file)
index b39e839..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-package main
-
-import (
-       "flag"
-       "fmt"
-       "os"
-)
-
-var exampleConfigFile = []byte(`
-Client:
-    APIHost: zzzzz.arvadosapi.com:443
-    AuthToken: xyzzy
-    Insecure: false
-KeepServiceTypes:
-    - disk
-Listen: ":9005"
-ManagementToken: xyzzy
-RunPeriod: 600s
-CollectionBatchSize: 100000
-CollectionBuffers: 1000
-RequestTimeout: 30m`)
-
-func usage() {
-       fmt.Fprintf(os.Stderr, `
-
-keep-balance rebalances a set of keepstore servers. It creates new
-copies of underreplicated blocks, deletes excess copies of
-overreplicated and unreferenced blocks, and moves blocks to better
-positions (according to the rendezvous hash algorithm) so clients find
-them faster.
-
-Usage: keep-balance [options]
-
-Options:
-`)
-       flag.PrintDefaults()
-       fmt.Fprintf(os.Stderr, `
-Example config file:
-%s
-
-    Client.AuthToken must be recognized by Arvados as an admin token,
-    and must be recognized by all Keep services as a "data manager
-    key".
-
-    Client.Insecure should be true if your Arvados API endpoint uses
-    an unverifiable SSL/TLS certificate.
-
-Periodic scanning:
-
-    By default, keep-balance operates periodically, i.e.: do a
-    scan/balance operation, sleep, repeat.
-
-    RunPeriod determines the interval between start times of
-    successive scan/balance operations. If a scan/balance operation
-    takes longer than RunPeriod, the next one will follow it
-    immediately.
-
-    If SIGUSR1 is received during an idle period between operations,
-    the next operation will start immediately.
-
-One-time scanning:
-
-    Use the -once flag to do a single operation and then exit. The
-    exit code will be zero if the operation was successful.
-
-Committing:
-
-    By default, keep-service computes and reports changes but does not
-    implement them by sending pull and trash lists to the Keep
-    services.
-
-    Use the -commit-pull and -commit-trash flags to implement the
-    computed changes.
-
-Tuning resource usage:
-
-    CollectionBatchSize limits the number of collections retrieved per
-    API transaction. If this is zero or omitted, page size is
-    determined by the API server's own page size limits (see
-    max_items_per_response and max_index_database_read configs).
-
-    CollectionBuffers sets the size of an internal queue of
-    collections. Higher values use more memory, and improve throughput
-    by allowing keep-balance to fetch the next page of collections
-    while the current page is still being processed. If this is zero
-    or omitted, pages are processed serially.
-
-    RequestTimeout is the maximum time keep-balance will spend on a
-    single HTTP request (getting a page of collections, getting the
-    block index from a keepstore server, or sending a trash or pull
-    list to a keepstore server). Defaults to 30 minutes.
-
-Limitations:
-
-    keep-balance does not attempt to discover whether committed pull
-    and trash requests ever get carried out -- only that they are
-    accepted by the Keep services. If some services are full, new
-    copies of underreplicated blocks might never get made, only
-    repeatedly requested.
-
-`, exampleConfigFile)
-}