Merge branch '14715-keepprox-config'
authorEric Biagiotti <ebiagiotti@veritasgenetics.com>
Thu, 22 Aug 2019 15:50:53 +0000 (11:50 -0400)
committerEric Biagiotti <ebiagiotti@veritasgenetics.com>
Thu, 22 Aug 2019 15:50:53 +0000 (11:50 -0400)
refs #14715

Arvados-DCO-1.1-Signed-off-by: Eric Biagiotti <ebiagiotti@veritasgenetics.com>

17 files changed:
doc/admin/config-migration.html.textile.liquid
doc/admin/upgrading.html.textile.liquid
doc/install/install-keepproxy.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
sdk/python/tests/run_test_server.py
services/keepproxy/keepproxy.go
services/keepproxy/keepproxy.service
services/keepproxy/keepproxy_test.go
services/keepproxy/usage.go [deleted file]
tools/arvbox/lib/arvbox/docker/cluster-config.sh
tools/arvbox/lib/arvbox/docker/service/keepproxy/run-service

index 3f10a87feb29927ed40a694e3d5ceffd85a9c145..556e20dd08ac5817643a024f01949d4b68070dd9 100644 (file)
@@ -62,6 +62,10 @@ h2. keepstore
 
 Currently only reads @RemoteClusters@ from centralized configuration.  Still requires component-specific configuration file.
 
+h2(#keepproxy). keepproxy
+
+The legacy keepproxy config (loaded from @/etc/arvados/keepproxy/keepproxy.yml@ or a different location specified via -legacy-keepproxy-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/keepproxy/keepproxy.yml@ and stop using the -legacy-keepproxy-config argument.
+
 h2. arvados-controller
 
 Already uses centralized config exclusively.  No migration needed.
index 8c2ca765769eb18c6eb79bbe078c0dcde8ba08bc..079a41d2f38797031498473b200ab8619f02d08b 100644 (file)
@@ -51,6 +51,10 @@ h4. Jobs API is read-only
 
 So that older Arvados sites don't lose access to legacy records, the API has been converted to read-only.  Creating and updating jobs (and related types job_task, pipeline_template and pipeline_instance) is disabled and much of the business logic related has been removed, along with various other code specific to the jobs API.  Specifically, the following programs associated with the jobs API have been removed: @crunch-dispatch.rb@, @crunch-job@, @crunchrunner@, @arv-run-pipeline-instance@, @arv-run@.
 
+h4. Keepproxy configuration migration
+
+(feature "#14715":https://dev.arvados.org/issues/14715 ) Keepproxy can now be configured using the centralized config at @/etc/arvados/config.yml@. Configuration via individual command line arguments is no longer available and the @DisableGet@, @DisablePut@, and @PIDFile@ configuration options are no longer supported. If you are still using the legacy config and @DisableGet@ or @DisablePut@ are set to true or @PIDFile@ has a value, keepproxy will produce an error and fail to start. Please see "keepproxy's config migration guide":{{site.baseurl}}/admin/config-migration.html#keepproxy for more details.
+
 h4. No longer stripping ':' from strings in serialized database columns
 
 (bug "#15311":https://dev.arvados.org/issues/15311 ) Strings read from serialized columns in the database with a leading ':' would have the ':' stripped after loading the record.  This behavior existed due to legacy serialization behavior which stored Ruby symbols with a leading ':'.  Unfortunately this corrupted fields where the leading ":" was intentional.  This behavior has been removed.
index db24953fccb17d35bbf1232f2caa34e19104cea6..910e47e0e8f90666b625a0a28f504b827759d3e8 100644 (file)
@@ -44,27 +44,30 @@ Verify that Keepproxy is functional:
 
 <notextile>
 <pre><code>~$ <span class="userinput">keepproxy -h</span>
-...
-Usage: keepproxy [-config path/to/keepproxy.yml]
-...
+Usage of keepproxy:
+  -config file
+       Site configuration file (default may be overridden by setting an ARVADOS_CONFIG environment variable) (default "/etc/arvados/config.yml")
+  -dump-config
+       write current configuration to stdout and exit
+[...]
+  -version
+       print version information and exit.
 </code></pre>
 </notextile>
 
-h3. Create an API token for the Keepproxy server
-
-{% assign railscmd = "bundle exec ./script/get_anonymous_user_token.rb --get" %}
-{% assign railsout = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" %}
-The Keepproxy server needs a token to talk to the API server.  On the <strong>API server</strong>, use the following command to create the token.  {% include 'install_rails_command' %}
-
-h3. Set up the Keepproxy service
+h3. Update the cluster config
 
-Install runit to supervise the keepproxy daemon.  {% include 'install_runit' %}
-
-The run script for the keepproxy service should set the environment variables @ARVADOS_API_TOKEN@ (with the token you just generated), @ARVADOS_API_HOST@, and, if needed, @ARVADOS_API_HOST_INSECURE@.  The core keepproxy command to run is:
+Edit the cluster config at @/etc/arvados/config.yml@ and set @Services.Keepproxy.ExternalURL@ and @Services.Keepproxy.InternalURLs@.  Replace @zzzzz@ with your cluster id.
 
 <notextile>
-<pre><code>ARVADOS_API_TOKEN=<span class="userinput">{{railsout}}</span> ARVADOS_API_HOST=<span class="userinput">uuid_prefix.your.domain</span> exec keepproxy
-</code></pre>
+<pre><code>Clusters:
+  <span class="userinput">uuid_prefix</span>:
+    Services:
+      <span class="userinput">Keepproxy:
+        ExternalURL: https://keep.uuid_prefix.your.domain
+        InternalURLs:
+         "http://localhost:25107": {}
+</span></code></pre>
 </notextile>
 
 h3. Set up a reverse proxy with SSL support
@@ -131,6 +134,32 @@ export ARVADOS_API_TOKEN=zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
 EOF</span>
 </code></pre></notextile>
 
+h2. Run Keepproxy
+
+h3. Start the service (option 1: systemd)
+
+If your system does not use systemd, skip this section and follow the "runit instructions":#runit instead.
+
+If your system uses systemd, the keepproxy service should already be set up. Start it and check its status:
+
+<notextile>
+<pre><code>~$ <span class="userinput">sudo systemctl restart keepproxy</span>
+~$ <span class="userinput">sudo systemctl status keepproxy</span>
+&#x25cf; keepproxy.service - Arvados Keep Proxy
+   Loaded: loaded (/lib/systemd/system/keepproxy.service; enabled)
+   Active: active (running) since Tue 2019-07-23 09:33:47 EDT; 3 weeks 1 days ago
+     Docs: https://doc.arvados.org/
+ Main PID: 1150 (Keepproxy)
+   CGroup: /system.slice/keepproxy.service
+           └─1150 /usr/bin/keepproxy
+[...]
+</code></pre>
+</notextile>
+
+h3(#runit). Start the service (option 2: runit)
+
+Install runit to supervise the Keep-web daemon.  {% include 'install_runit' %}
+
 h3. Testing keepproxy
 
 Log into a host that is on an external network from your private Arvados network.  The host should be able to contact your keepproxy server (eg keep.$uuid_prefix.arvadosapi.com), but not your keepstore servers (eg keep[0-9].$uuid_prefix.arvadosapi.com).
index dfdd0310448826174b83c1e005d2d22dcce9e062..163cd87ec5107ee826e1b971eac94fc4f8b1892d 100644 (file)
@@ -206,6 +206,9 @@ Clusters:
       WebsocketClientEventQueue: 64
       WebsocketServerEventQueue: 4
 
+      # Timeout on requests to internal Keep services.
+      KeepServiceRequestTimeout: 15s
+
     Users:
       # Config parameters to automatically setup new users.  If enabled,
       # this users will be able to self-activate.  Enable this if you want
index 019979d39fe2d068c4a196d398e5111d137c35c1..df872111db2adf8ff1bc504fd0a5b2f31f2b14a6 100644 (file)
@@ -327,6 +327,78 @@ func (ldr *Loader) loadOldWebsocketConfig(cfg *arvados.Config) error {
        return nil
 }
 
+type oldKeepProxyConfig struct {
+       Client          *arvados.Client
+       Listen          *string
+       DisableGet      *bool
+       DisablePut      *bool
+       DefaultReplicas *int
+       Timeout         *arvados.Duration
+       PIDFile         *string
+       Debug           *bool
+       ManagementToken *string
+}
+
+const defaultKeepproxyConfigPath = "/etc/arvados/keepproxy/keepproxy.yml"
+
+func (ldr *Loader) loadOldKeepproxyConfig(cfg *arvados.Config) error {
+       if ldr.KeepproxyPath == "" {
+               return nil
+       }
+       var oc oldKeepProxyConfig
+       err := ldr.loadOldConfigHelper("keepproxy", ldr.KeepproxyPath, &oc)
+       if os.IsNotExist(err) && ldr.KeepproxyPath == defaultKeepproxyConfigPath {
+               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.Keepproxy.InternalURLs[arvados.URL{Host: *oc.Listen}] = arvados.ServiceInstance{}
+       }
+       if oc.DefaultReplicas != nil {
+               cluster.Collections.DefaultReplication = *oc.DefaultReplicas
+       }
+       if oc.Timeout != nil {
+               cluster.API.KeepServiceRequestTimeout = *oc.Timeout
+       }
+       if oc.Debug != nil {
+               if *oc.Debug && cluster.SystemLogs.LogLevel != "debug" {
+                       cluster.SystemLogs.LogLevel = "debug"
+               } else if !*oc.Debug && cluster.SystemLogs.LogLevel != "info" {
+                       cluster.SystemLogs.LogLevel = "info"
+               }
+       }
+       if oc.ManagementToken != nil {
+               cluster.ManagementToken = *oc.ManagementToken
+       }
+
+       // The following legacy options are no longer supported. If they are set to
+       // true or PIDFile has a value, error out and notify the user
+       unsupportedEntry := func(cfgEntry string) error {
+               return fmt.Errorf("the keepproxy %s configuration option is no longer supported, please remove it from your configuration file", cfgEntry)
+       }
+       if oc.DisableGet != nil && *oc.DisableGet {
+               return unsupportedEntry("DisableGet")
+       }
+       if oc.DisablePut != nil && *oc.DisablePut {
+               return unsupportedEntry("DisablePut")
+       }
+       if oc.PIDFile != nil && *oc.PIDFile != "" {
+               return unsupportedEntry("PIDFile")
+       }
+
+       cfg.Clusters[cluster.ClusterID] = *cluster
+       return nil
+}
+
 const defaultKeepWebConfigPath = "/etc/arvados/keep-web/keep-web.yml"
 
 type oldKeepWebConfig struct {
index 8479842be9a3bf1255ff3777373b11468f7bbf8d..76313ebb1615885298b3c8109ea73515fcfa8f2f 100644 (file)
@@ -6,6 +6,7 @@ package config
 
 import (
        "flag"
+       "fmt"
        "io/ioutil"
        "os"
        "time"
@@ -14,6 +15,35 @@ import (
        check "gopkg.in/check.v1"
 )
 
+func testLoadLegacyConfig(content []byte, mungeFlag string, c *check.C) (*arvados.Cluster, error) {
+       tmpfile, err := ioutil.TempFile("", "example")
+       if err != nil {
+               return nil, err
+       }
+       defer os.Remove(tmpfile.Name())
+
+       if _, err := tmpfile.Write(content); err != nil {
+               return nil, err
+       }
+       if err := tmpfile.Close(); err != nil {
+               return nil, err
+       }
+       flags := flag.NewFlagSet("test", flag.ExitOnError)
+       ldr := testLoader(c, "Clusters: {zzzzz: {}}", nil)
+       ldr.SetupFlags(flags)
+       args := ldr.MungeLegacyConfigArgs(ldr.Logger, []string{"-config", tmpfile.Name()}, mungeFlag)
+       flags.Parse(args)
+       cfg, err := ldr.Load()
+       if err != nil {
+               return nil, err
+       }
+       cluster, err := cfg.GetCluster("")
+       if err != nil {
+               return nil, err
+       }
+       return cluster, nil
+}
+
 func (s *LoadSuite) TestDeprecatedNodeProfilesToServices(c *check.C) {
        hostname, err := os.Hostname()
        c.Assert(err, check.IsNil)
@@ -81,33 +111,8 @@ func (s *LoadSuite) TestLegacyKeepWebConfig(c *check.C) {
        "ManagementToken": "xyzzy"
 }
 `)
-       tmpfile, err := ioutil.TempFile("", "example")
-       if err != nil {
-               c.Error(err)
-       }
-       defer os.Remove(tmpfile.Name())
-
-       if _, err := tmpfile.Write(content); err != nil {
-               c.Error(err)
-       }
-       if err := tmpfile.Close(); err != nil {
-               c.Error(err)
-       }
-       flags := flag.NewFlagSet("keep-web", flag.ExitOnError)
-       ldr := testLoader(c, "Clusters: {zzzzz: {}}", nil)
-       ldr.SetupFlags(flags)
-       args := ldr.MungeLegacyConfigArgs(ldr.Logger, []string{"-config", tmpfile.Name()}, "-legacy-keepweb-config")
-       flags.Parse(args)
-       cfg, err := ldr.Load()
-       if err != nil {
-               c.Error(err)
-       }
-       c.Check(cfg, check.NotNil)
-       cluster, err := cfg.GetCluster("")
-       if err != nil {
-               c.Error(err)
-       }
-       c.Check(cluster, check.NotNil)
+       cluster, err := testLoadLegacyConfig(content, "-legacy-keepweb-config", c)
+       c.Check(err, check.IsNil)
 
        c.Check(cluster.Services.Controller.ExternalURL, check.Equals, arvados.URL{Scheme: "https", Host: "example.com"})
        c.Check(cluster.SystemRootToken, check.Equals, "abcdefg")
@@ -127,3 +132,58 @@ func (s *LoadSuite) TestLegacyKeepWebConfig(c *check.C) {
        c.Check(cluster.Users.AnonymousUserToken, check.Equals, "anonusertoken")
        c.Check(cluster.ManagementToken, check.Equals, "xyzzy")
 }
+
+func (s *LoadSuite) TestLegacyKeepproxyConfig(c *check.C) {
+       f := "-legacy-keepproxy-config"
+       content := []byte(fmtKeepproxyConfig("", true))
+       cluster, err := testLoadLegacyConfig(content, f, c)
+
+       c.Check(err, check.IsNil)
+       c.Check(cluster, check.NotNil)
+       c.Check(cluster.Services.Controller.ExternalURL, check.Equals, arvados.URL{Scheme: "https", Host: "example.com"})
+       c.Check(cluster.SystemRootToken, check.Equals, "abcdefg")
+       c.Check(cluster.ManagementToken, check.Equals, "xyzzy")
+       c.Check(cluster.Services.Keepproxy.InternalURLs[arvados.URL{Host: ":80"}], check.Equals, arvados.ServiceInstance{})
+       c.Check(cluster.Collections.DefaultReplication, check.Equals, 0)
+       c.Check(cluster.API.KeepServiceRequestTimeout.String(), check.Equals, "15s")
+       c.Check(cluster.SystemLogs.LogLevel, check.Equals, "debug")
+
+       content = []byte(fmtKeepproxyConfig("", false))
+       cluster, err = testLoadLegacyConfig(content, f, c)
+       c.Check(cluster.SystemLogs.LogLevel, check.Equals, "info")
+
+       content = []byte(fmtKeepproxyConfig(`"DisableGet": true,`, true))
+       _, err = testLoadLegacyConfig(content, f, c)
+       c.Check(err, check.NotNil)
+
+       content = []byte(fmtKeepproxyConfig(`"DisablePut": true,`, true))
+       _, err = testLoadLegacyConfig(content, f, c)
+       c.Check(err, check.NotNil)
+
+       content = []byte(fmtKeepproxyConfig(`"PIDFile": "test",`, true))
+       _, err = testLoadLegacyConfig(content, f, c)
+       c.Check(err, check.NotNil)
+
+       content = []byte(fmtKeepproxyConfig(`"DisableGet": false, "DisablePut": false, "PIDFile": "",`, true))
+       _, err = testLoadLegacyConfig(content, f, c)
+       c.Check(err, check.IsNil)
+}
+
+func fmtKeepproxyConfig(param string, debugLog bool) string {
+       return fmt.Sprintf(`
+{
+       "Client": {
+               "Scheme": "",
+               "APIHost": "example.com",
+               "AuthToken": "abcdefg",
+               "Insecure": false
+       },
+       "Listen": ":80",
+       "DefaultReplicas": 0,
+       "Timeout": "15s",
+       "Debug": %t,
+       %s
+       "ManagementToken": "xyzzy"
+}
+`, debugLog, param)
+}
index a0be827f040e61dd8052def574a589404fab216d..6eb4fbe5f570d4abf9ffd885f1ab0d822acf7fa2 100644 (file)
@@ -72,6 +72,7 @@ var whitelist = map[string]bool{
        "API.WebsocketClientEventQueue":                false,
        "API.SendTimeout":                              true,
        "API.WebsocketServerEventQueue":                false,
+       "API.KeepServiceRequestTimeout":                false,
        "AuditLogs":                                    false,
        "AuditLogs.MaxAge":                             false,
        "AuditLogs.MaxDeleteBatch":                     false,
index 6cb8bf81aee3607d6bce79986e466d0b86eb807e..1eae24d84e540ccc597cc0dcf8f048fadf19514e 100644 (file)
@@ -212,6 +212,9 @@ Clusters:
       WebsocketClientEventQueue: 64
       WebsocketServerEventQueue: 4
 
+      # Timeout on requests to internal Keep services.
+      KeepServiceRequestTimeout: 15s
+
     Users:
       # Config parameters to automatically setup new users.  If enabled,
       # this users will be able to self-activate.  Enable this if you want
index 3413e3bec3c0418098d39f10c984c20e25c48d69..84de9b60e991de1672ddb4c448faf860d8ee64fb 100644 (file)
@@ -34,6 +34,7 @@ type Loader struct {
        KeepWebPath             string
        CrunchDispatchSlurmPath string
        WebsocketPath           string
+       KeepproxyPath           string
 
        configdata []byte
 }
@@ -64,6 +65,7 @@ func (ldr *Loader) SetupFlags(flagset *flag.FlagSet) {
        flagset.StringVar(&ldr.KeepWebPath, "legacy-keepweb-config", defaultKeepWebConfigPath, "Legacy keep-web configuration `file`")
        flagset.StringVar(&ldr.CrunchDispatchSlurmPath, "legacy-crunch-dispatch-slurm-config", defaultCrunchDispatchSlurmConfigPath, "Legacy crunch-dispatch-slurm configuration `file`")
        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.BoolVar(&ldr.SkipLegacy, "skip-legacy", false, "Don't load legacy config files")
 }
 
@@ -138,6 +140,9 @@ func (ldr *Loader) MungeLegacyConfigArgs(lgr logrus.FieldLogger, args []string,
        if legacyConfigArg != "-legacy-keepweb-config" {
                ldr.KeepWebPath = ""
        }
+       if legacyConfigArg != "-legacy-keepproxy-config" {
+               ldr.KeepproxyPath = ""
+       }
 
        return munged
 }
@@ -238,6 +243,7 @@ func (ldr *Loader) Load() (*arvados.Config, error) {
                        ldr.loadOldKeepWebConfig(&cfg),
                        ldr.loadOldCrunchDispatchSlurmConfig(&cfg),
                        ldr.loadOldWebsocketConfig(&cfg),
+                       ldr.loadOldKeepproxyConfig(&cfg),
                } {
                        if err != nil {
                                return nil, err
index 02043fb6d192e63a0e438b9f12981efbd259df70..93f52f3c809bb003c3bb506574d42e140728960c 100644 (file)
@@ -88,6 +88,7 @@ type Cluster struct {
                SendTimeout                    Duration
                WebsocketClientEventQueue      int
                WebsocketServerEventQueue      int
+               KeepServiceRequestTimeout      Duration
        }
        AuditLogs struct {
                MaxAge             Duration
index 80227e6cd0a79335e7486beac156c72cde4b8d87..e79b4843a268049e117d60e59a37a8de62e61fbb 100644 (file)
@@ -544,10 +544,11 @@ def run_keep_proxy():
     env['ARVADOS_API_TOKEN'] = auth_token('anonymous')
     logf = open(_logfilename('keepproxy'), 'a')
     kp = subprocess.Popen(
-        ['keepproxy',
-         '-pid='+_pidfile('keepproxy'),
-         '-listen=:{}'.format(port)],
-        env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
+        ['keepproxy'], env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
+
+    with open(_pidfile('keepproxy'), 'w') as f:
+        f.write(str(kp.pid))
+    _wait_until_port_listens(port)
 
     print("Using API %s token %s" % (os.environ['ARVADOS_API_HOST'], auth_token('admin')), file=sys.stdout)
     api = arvados.api(
index f8aa6c4aa7db3df87e7e598aaa901e8e3e91763c..9244fe00cb165b174005383163b2f24f0a22331e 100644 (file)
@@ -20,9 +20,9 @@ import (
        "syscall"
        "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/config"
        "git.curoverse.com/arvados.git/sdk/go/health"
        "git.curoverse.com/arvados.git/sdk/go/httpserver"
        "git.curoverse.com/arvados.git/sdk/go/keepclient"
@@ -34,25 +34,6 @@ import (
 
 var version = "dev"
 
-type Config struct {
-       Client          arvados.Client
-       Listen          string
-       DisableGet      bool
-       DisablePut      bool
-       DefaultReplicas int
-       Timeout         arvados.Duration
-       PIDFile         string
-       Debug           bool
-       ManagementToken string
-}
-
-func DefaultConfig() *Config {
-       return &Config{
-               Listen:  ":25107",
-               Timeout: arvados.Duration(15 * time.Second),
-       }
-}
-
 var (
        listener net.Listener
        router   http.Handler
@@ -60,114 +41,109 @@ var (
 
 const rfc3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00"
 
-func main() {
-       log.SetFormatter(&log.JSONFormatter{
-               TimestampFormat: rfc3339NanoFixed,
-       })
+func configure(logger log.FieldLogger, args []string) (*arvados.Cluster, error) {
+       flags := flag.NewFlagSet(args[0], flag.ExitOnError)
 
-       cfg := DefaultConfig()
+       dumpConfig := flags.Bool("dump-config", false, "write current configuration to stdout and exit")
+       getVersion := flags.Bool("version", false, "Print version information and exit.")
 
-       flagset := flag.NewFlagSet("keepproxy", flag.ExitOnError)
-       flagset.Usage = usage
+       loader := config.NewLoader(os.Stdin, logger)
+       loader.SetupFlags(flags)
 
-       const deprecated = " (DEPRECATED -- use config file instead)"
-       flagset.StringVar(&cfg.Listen, "listen", cfg.Listen, "Local port to listen on."+deprecated)
-       flagset.BoolVar(&cfg.DisableGet, "no-get", cfg.DisableGet, "Disable GET operations."+deprecated)
-       flagset.BoolVar(&cfg.DisablePut, "no-put", cfg.DisablePut, "Disable PUT operations."+deprecated)
-       flagset.IntVar(&cfg.DefaultReplicas, "default-replicas", cfg.DefaultReplicas, "Default number of replicas to write if not specified by the client. If 0, use site default."+deprecated)
-       flagset.StringVar(&cfg.PIDFile, "pid", cfg.PIDFile, "Path to write pid file."+deprecated)
-       timeoutSeconds := flagset.Int("timeout", int(time.Duration(cfg.Timeout)/time.Second), "Timeout (in seconds) on requests to internal Keep services."+deprecated)
-       flagset.StringVar(&cfg.ManagementToken, "management-token", cfg.ManagementToken, "Authorization token to be included in all health check requests.")
-
-       var cfgPath string
-       const defaultCfgPath = "/etc/arvados/keepproxy/keepproxy.yml"
-       flagset.StringVar(&cfgPath, "config", defaultCfgPath, "Configuration file `path`")
-       dumpConfig := flagset.Bool("dump-config", false, "write current configuration to stdout and exit")
-       getVersion := flagset.Bool("version", false, "Print version information and exit.")
-       flagset.Parse(os.Args[1:])
+       args = loader.MungeLegacyConfigArgs(logger, args[1:], "-legacy-keepproxy-config")
+       flags.Parse(args)
 
        // Print version information if requested
        if *getVersion {
                fmt.Printf("keepproxy %s\n", version)
-               return
+               return nil, nil
        }
 
-       err := config.LoadFile(cfg, cfgPath)
+       cfg, err := loader.Load()
        if err != nil {
-               h := os.Getenv("ARVADOS_API_HOST")
-               t := os.Getenv("ARVADOS_API_TOKEN")
-               if h == "" || t == "" || !os.IsNotExist(err) || cfgPath != defaultCfgPath {
-                       log.Fatal(err)
-               }
-               log.Print("DEPRECATED: No config file found, but ARVADOS_API_HOST and ARVADOS_API_TOKEN environment variables are set. Please use a config file instead.")
-               cfg.Client.APIHost = h
-               cfg.Client.AuthToken = t
-               if regexp.MustCompile("^(?i:1|yes|true)$").MatchString(os.Getenv("ARVADOS_API_HOST_INSECURE")) {
-                       cfg.Client.Insecure = true
+               return nil, err
+       }
+       cluster, err := cfg.GetCluster("")
+       if err != nil {
+               return nil, err
+       }
+
+       if *dumpConfig {
+               out, err := yaml.Marshal(cfg)
+               if err != nil {
+                       return nil, err
                }
-               if y, err := yaml.Marshal(cfg); err == nil && !*dumpConfig {
-                       log.Print("Current configuration:\n", string(y))
+               if _, err := os.Stdout.Write(out); err != nil {
+                       return nil, err
                }
-               cfg.Timeout = arvados.Duration(time.Duration(*timeoutSeconds) * time.Second)
+               return nil, nil
        }
+       return cluster, nil
+}
 
-       if *dumpConfig {
-               log.Fatal(config.DumpAndExit(cfg))
+func main() {
+       logger := log.New()
+       logger.Formatter = &log.JSONFormatter{
+               TimestampFormat: rfc3339NanoFixed,
+       }
+
+       cluster, err := configure(logger, os.Args)
+       if err != nil {
+               log.Fatal(err)
+       }
+       if cluster == nil {
+               return
        }
 
        log.Printf("keepproxy %s started", version)
 
-       arv, err := arvadosclient.New(&cfg.Client)
+       if err := run(logger, cluster); err != nil {
+               log.Fatal(err)
+       }
+
+       log.Println("shutting down")
+}
+
+func run(logger log.FieldLogger, cluster *arvados.Cluster) error {
+       client, err := arvados.NewClientFromConfig(cluster)
+       if err != nil {
+               return err
+       }
+       client.AuthToken = cluster.SystemRootToken
+
+       arv, err := arvadosclient.New(client)
        if err != nil {
-               log.Fatalf("Error setting up arvados client %s", err.Error())
+               return fmt.Errorf("Error setting up arvados client %v", err)
        }
 
-       if cfg.Debug {
+       if cluster.SystemLogs.LogLevel == "debug" {
                keepclient.DebugPrintf = log.Printf
        }
        kc, err := keepclient.MakeKeepClient(arv)
        if err != nil {
-               log.Fatalf("Error setting up keep client %s", err.Error())
+               return fmt.Errorf("Error setting up keep client %v", err)
        }
        keepclient.RefreshServiceDiscoveryOnSIGHUP()
 
-       if cfg.PIDFile != "" {
-               f, err := os.Create(cfg.PIDFile)
-               if err != nil {
-                       log.Fatal(err)
-               }
-               defer f.Close()
-               err = syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
-               if err != nil {
-                       log.Fatalf("flock(%s): %s", cfg.PIDFile, err)
-               }
-               defer os.Remove(cfg.PIDFile)
-               err = f.Truncate(0)
-               if err != nil {
-                       log.Fatalf("truncate(%s): %s", cfg.PIDFile, err)
-               }
-               _, err = fmt.Fprint(f, os.Getpid())
-               if err != nil {
-                       log.Fatalf("write(%s): %s", cfg.PIDFile, err)
-               }
-               err = f.Sync()
-               if err != nil {
-                       log.Fatalf("sync(%s): %s", cfg.PIDFile, err)
-               }
+       if cluster.Collections.DefaultReplication > 0 {
+               kc.Want_replicas = cluster.Collections.DefaultReplication
        }
 
-       if cfg.DefaultReplicas > 0 {
-               kc.Want_replicas = cfg.DefaultReplicas
+       var listen arvados.URL
+       for listen = range cluster.Services.Keepproxy.InternalURLs {
+               break
        }
 
-       listener, err = net.Listen("tcp", cfg.Listen)
-       if err != nil {
-               log.Fatalf("listen(%s): %s", cfg.Listen, err)
+       var lErr error
+       listener, lErr = net.Listen("tcp", listen.Host)
+       if lErr != nil {
+               return fmt.Errorf("listen(%s): %v", listen.Host, lErr)
        }
+
        if _, err := daemon.SdNotify(false, "READY=1"); err != nil {
                log.Printf("Error notifying init daemon: %v", err)
        }
-       log.Println("Listening at", listener.Addr())
+       log.Println("listening at", listener.Addr())
 
        // Shut down the server gracefully (by closing the listener)
        // if SIGTERM is received.
@@ -181,10 +157,8 @@ func main() {
        signal.Notify(term, syscall.SIGINT)
 
        // Start serving requests.
-       router = MakeRESTRouter(!cfg.DisableGet, !cfg.DisablePut, kc, time.Duration(cfg.Timeout), cfg.ManagementToken)
-       http.Serve(listener, httpserver.AddRequestIDs(httpserver.LogRequests(router)))
-
-       log.Println("shutting down")
+       router = MakeRESTRouter(kc, time.Duration(cluster.API.KeepServiceRequestTimeout), cluster.SystemRootToken)
+       return http.Serve(listener, httpserver.AddRequestIDs(httpserver.LogRequests(router)))
 }
 
 type ApiTokenCache struct {
@@ -292,7 +266,7 @@ type proxyHandler struct {
 
 // MakeRESTRouter returns an http.Handler that passes GET and PUT
 // requests to the appropriate handlers.
-func MakeRESTRouter(enable_get bool, enable_put bool, kc *keepclient.KeepClient, timeout time.Duration, mgmtToken string) http.Handler {
+func MakeRESTRouter(kc *keepclient.KeepClient, timeout time.Duration, mgmtToken string) http.Handler {
        rest := mux.NewRouter()
 
        transport := defaultTransport
@@ -315,24 +289,20 @@ func MakeRESTRouter(enable_get bool, enable_put bool, kc *keepclient.KeepClient,
                },
        }
 
-       if enable_get {
-               rest.HandleFunc(`/{locator:[0-9a-f]{32}\+.*}`, h.Get).Methods("GET", "HEAD")
-               rest.HandleFunc(`/{locator:[0-9a-f]{32}}`, h.Get).Methods("GET", "HEAD")
+       rest.HandleFunc(`/{locator:[0-9a-f]{32}\+.*}`, h.Get).Methods("GET", "HEAD")
+       rest.HandleFunc(`/{locator:[0-9a-f]{32}}`, h.Get).Methods("GET", "HEAD")
 
-               // List all blocks
-               rest.HandleFunc(`/index`, h.Index).Methods("GET")
+       // List all blocks
+       rest.HandleFunc(`/index`, h.Index).Methods("GET")
 
-               // List blocks whose hash has the given prefix
-               rest.HandleFunc(`/index/{prefix:[0-9a-f]{0,32}}`, h.Index).Methods("GET")
-       }
+       // List blocks whose hash has the given prefix
+       rest.HandleFunc(`/index/{prefix:[0-9a-f]{0,32}}`, h.Index).Methods("GET")
 
-       if enable_put {
-               rest.HandleFunc(`/{locator:[0-9a-f]{32}\+.*}`, h.Put).Methods("PUT")
-               rest.HandleFunc(`/{locator:[0-9a-f]{32}}`, h.Put).Methods("PUT")
-               rest.HandleFunc(`/`, h.Put).Methods("POST")
-               rest.HandleFunc(`/{any}`, h.Options).Methods("OPTIONS")
-               rest.HandleFunc(`/`, h.Options).Methods("OPTIONS")
-       }
+       rest.HandleFunc(`/{locator:[0-9a-f]{32}\+.*}`, h.Put).Methods("PUT")
+       rest.HandleFunc(`/{locator:[0-9a-f]{32}}`, h.Put).Methods("PUT")
+       rest.HandleFunc(`/`, h.Put).Methods("POST")
+       rest.HandleFunc(`/{any}`, h.Options).Methods("OPTIONS")
+       rest.HandleFunc(`/`, h.Options).Methods("OPTIONS")
 
        rest.Handle("/_health/{check}", &health.Handler{
                Token:  mgmtToken,
index 96dec25ecf77ecc8c3936628829d8dc59aecabc4..1d0113e0e43a04a17fa2e7ae9657f1ed7bc9f1fa 100644 (file)
@@ -6,7 +6,6 @@
 Description=Arvados Keep Proxy
 Documentation=https://doc.arvados.org/
 After=network.target
-AssertPathExists=/etc/arvados/keepproxy/keepproxy.yml
 
 # systemd==229 (ubuntu:xenial) obeys StartLimitInterval in the [Unit] section
 StartLimitInterval=0
index dc70d968e2992a16581694ac70bbf42ba92f93ba..d2758cc25f7ea3f92b35ec461f0d41c87958c72e 100644 (file)
@@ -13,15 +13,17 @@ import (
        "math/rand"
        "net/http"
        "net/http/httptest"
-       "os"
        "strings"
        "sync"
        "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/keepclient"
+       log "github.com/sirupsen/logrus"
 
        . "gopkg.in/check.v1"
 )
@@ -96,14 +98,23 @@ func (s *NoKeepServerSuite) TearDownSuite(c *C) {
        arvadostest.StopAPI()
 }
 
-func runProxy(c *C, args []string, bogusClientToken bool) *keepclient.KeepClient {
-       args = append([]string{"keepproxy"}, args...)
-       os.Args = append(args, "-listen=:0")
+func runProxy(c *C, bogusClientToken bool) *keepclient.KeepClient {
+       cfg, err := config.NewLoader(nil, nil).Load()
+       c.Assert(err, Equals, nil)
+       cluster, err := cfg.GetCluster("")
+       c.Assert(err, Equals, nil)
+
+       cluster.Services.Keepproxy.InternalURLs = map[arvados.URL]arvados.ServiceInstance{arvados.URL{Host: ":0"}: arvados.ServiceInstance{}}
+
        listener = nil
-       go main()
+       go func() {
+               run(log.New(), cluster)
+               defer closeListener()
+       }()
        waitForListener()
 
-       arv, err := arvadosclient.MakeArvadosClient()
+       client := arvados.NewClientFromEnv()
+       arv, err := arvadosclient.New(client)
        c.Assert(err, Equals, nil)
        if bogusClientToken {
                arv.ApiToken = "bogus-token"
@@ -119,7 +130,7 @@ func runProxy(c *C, args []string, bogusClientToken bool) *keepclient.KeepClient
 }
 
 func (s *ServerRequiredSuite) TestResponseViaHeader(c *C) {
-       runProxy(c, nil, false)
+       runProxy(c, false)
        defer closeListener()
 
        req, err := http.NewRequest("POST",
@@ -145,7 +156,7 @@ func (s *ServerRequiredSuite) TestResponseViaHeader(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestLoopDetection(c *C) {
-       kc := runProxy(c, nil, false)
+       kc := runProxy(c, false)
        defer closeListener()
 
        sr := map[string]string{
@@ -163,7 +174,7 @@ func (s *ServerRequiredSuite) TestLoopDetection(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestStorageClassesHeader(c *C) {
-       kc := runProxy(c, nil, false)
+       kc := runProxy(c, false)
        defer closeListener()
 
        // Set up fake keepstore to record request headers
@@ -190,7 +201,7 @@ func (s *ServerRequiredSuite) TestStorageClassesHeader(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestDesiredReplicas(c *C) {
-       kc := runProxy(c, nil, false)
+       kc := runProxy(c, false)
        defer closeListener()
 
        content := []byte("TestDesiredReplicas")
@@ -207,7 +218,7 @@ func (s *ServerRequiredSuite) TestDesiredReplicas(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestPutWrongContentLength(c *C) {
-       kc := runProxy(c, nil, false)
+       kc := runProxy(c, false)
        defer closeListener()
 
        content := []byte("TestPutWrongContentLength")
@@ -218,7 +229,7 @@ func (s *ServerRequiredSuite) TestPutWrongContentLength(c *C) {
        // fixes the invalid Content-Length header. In order to test
        // our server behavior, we have to call the handler directly
        // using an httptest.ResponseRecorder.
-       rtr := MakeRESTRouter(true, true, kc, 10*time.Second, "")
+       rtr := MakeRESTRouter(kc, 10*time.Second, "")
 
        type testcase struct {
                sendLength   string
@@ -246,7 +257,7 @@ func (s *ServerRequiredSuite) TestPutWrongContentLength(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestManyFailedPuts(c *C) {
-       kc := runProxy(c, nil, false)
+       kc := runProxy(c, false)
        defer closeListener()
        router.(*proxyHandler).timeout = time.Nanosecond
 
@@ -273,7 +284,7 @@ func (s *ServerRequiredSuite) TestManyFailedPuts(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestPutAskGet(c *C) {
-       kc := runProxy(c, nil, false)
+       kc := runProxy(c, false)
        defer closeListener()
 
        hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
@@ -350,7 +361,7 @@ func (s *ServerRequiredSuite) TestPutAskGet(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestPutAskGetForbidden(c *C) {
-       kc := runProxy(c, nil, true)
+       kc := runProxy(c, true)
        defer closeListener()
 
        hash := fmt.Sprintf("%x+3", md5.Sum([]byte("bar")))
@@ -375,59 +386,8 @@ func (s *ServerRequiredSuite) TestPutAskGetForbidden(c *C) {
 
 }
 
-func (s *ServerRequiredSuite) TestGetDisabled(c *C) {
-       kc := runProxy(c, []string{"-no-get"}, false)
-       defer closeListener()
-
-       hash := fmt.Sprintf("%x", md5.Sum([]byte("baz")))
-
-       {
-               _, _, err := kc.Ask(hash)
-               errNotFound, _ := err.(keepclient.ErrNotFound)
-               c.Check(errNotFound, NotNil)
-               c.Assert(err, ErrorMatches, `.*HTTP 405.*`)
-               c.Log("Ask 1")
-       }
-
-       {
-               hash2, rep, err := kc.PutB([]byte("baz"))
-               c.Check(hash2, Matches, fmt.Sprintf(`^%s\+3(\+.+)?$`, hash))
-               c.Check(rep, Equals, 2)
-               c.Check(err, Equals, nil)
-               c.Log("PutB")
-       }
-
-       {
-               blocklen, _, err := kc.Ask(hash)
-               errNotFound, _ := err.(keepclient.ErrNotFound)
-               c.Check(errNotFound, NotNil)
-               c.Assert(err, ErrorMatches, `.*HTTP 405.*`)
-               c.Check(blocklen, Equals, int64(0))
-               c.Log("Ask 2")
-       }
-
-       {
-               _, blocklen, _, err := kc.Get(hash)
-               errNotFound, _ := err.(keepclient.ErrNotFound)
-               c.Check(errNotFound, NotNil)
-               c.Assert(err, ErrorMatches, `.*HTTP 405.*`)
-               c.Check(blocklen, Equals, int64(0))
-               c.Log("Get")
-       }
-}
-
-func (s *ServerRequiredSuite) TestPutDisabled(c *C) {
-       kc := runProxy(c, []string{"-no-put"}, false)
-       defer closeListener()
-
-       hash2, rep, err := kc.PutB([]byte("quux"))
-       c.Check(hash2, Equals, "")
-       c.Check(rep, Equals, 0)
-       c.Check(err, FitsTypeOf, keepclient.InsufficientReplicasError(errors.New("")))
-}
-
 func (s *ServerRequiredSuite) TestCorsHeaders(c *C) {
-       runProxy(c, nil, false)
+       runProxy(c, false)
        defer closeListener()
 
        {
@@ -458,7 +418,7 @@ func (s *ServerRequiredSuite) TestCorsHeaders(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestPostWithoutHash(c *C) {
-       runProxy(c, nil, false)
+       runProxy(c, false)
        defer closeListener()
 
        {
@@ -501,7 +461,7 @@ func (s *ServerRequiredSuite) TestStripHint(c *C) {
 //   With a valid but non-existing prefix (expect "\n")
 //   With an invalid prefix (expect error)
 func (s *ServerRequiredSuite) TestGetIndex(c *C) {
-       kc := runProxy(c, nil, false)
+       kc := runProxy(c, false)
        defer closeListener()
 
        // Put "index-data" blocks
@@ -564,7 +524,7 @@ func (s *ServerRequiredSuite) TestGetIndex(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestCollectionSharingToken(c *C) {
-       kc := runProxy(c, nil, false)
+       kc := runProxy(c, false)
        defer closeListener()
        hash, _, err := kc.PutB([]byte("shareddata"))
        c.Check(err, IsNil)
@@ -577,7 +537,7 @@ func (s *ServerRequiredSuite) TestCollectionSharingToken(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestPutAskGetInvalidToken(c *C) {
-       kc := runProxy(c, nil, false)
+       kc := runProxy(c, false)
        defer closeListener()
 
        // Put a test block
@@ -614,7 +574,7 @@ func (s *ServerRequiredSuite) TestPutAskGetInvalidToken(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestAskGetKeepProxyConnectionError(c *C) {
-       kc := runProxy(c, nil, false)
+       kc := runProxy(c, false)
        defer closeListener()
 
        // Point keepproxy at a non-existent keepstore
@@ -640,7 +600,7 @@ func (s *ServerRequiredSuite) TestAskGetKeepProxyConnectionError(c *C) {
 }
 
 func (s *NoKeepServerSuite) TestAskGetNoKeepServerError(c *C) {
-       kc := runProxy(c, nil, false)
+       kc := runProxy(c, false)
        defer closeListener()
 
        hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
@@ -663,10 +623,10 @@ func (s *NoKeepServerSuite) TestAskGetNoKeepServerError(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestPing(c *C) {
-       kc := runProxy(c, nil, false)
+       kc := runProxy(c, false)
        defer closeListener()
 
-       rtr := MakeRESTRouter(true, true, kc, 10*time.Second, arvadostest.ManagementToken)
+       rtr := MakeRESTRouter(kc, 10*time.Second, arvadostest.ManagementToken)
 
        req, err := http.NewRequest("GET",
                "http://"+listener.Addr().String()+"/_health/ping",
diff --git a/services/keepproxy/usage.go b/services/keepproxy/usage.go
deleted file mode 100644 (file)
index 6d3d21e..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-package main
-
-import (
-       "encoding/json"
-       "flag"
-       "fmt"
-       "os"
-)
-
-func usage() {
-       c := DefaultConfig()
-       c.Client.APIHost = "zzzzz.arvadosapi.com:443"
-       exampleConfigFile, err := json.MarshalIndent(c, "    ", "  ")
-       if err != nil {
-               panic(err)
-       }
-       fmt.Fprintf(os.Stderr, `
-
-Keepproxy forwards GET and PUT requests to keepstore servers.  See
-http://doc.arvados.org/install/install-keepproxy.html
-
-Usage: keepproxy [-config path/to/keepproxy.yml]
-
-Options:
-`)
-       flag.PrintDefaults()
-       fmt.Fprintf(os.Stderr, `
-Example config file:
-    %s
-
-Client.APIHost:
-
-    Address (or address:port) of the Arvados API endpoint.
-
-Client.AuthToken:
-
-    Anonymous API token.
-
-Client.Insecure:
-
-    True if your Arvados API endpoint uses an unverifiable SSL/TLS
-    certificate.
-
-Listen:
-
-    Local port to listen on. Can be "address:port" or ":port", where
-    "address" is a host IP address or name and "port" is a port number
-    or name.
-
-DisableGet:
-
-    Respond 404 to GET and HEAD requests.
-
-DisablePut:
-
-    Respond 404 to PUT, POST, and OPTIONS requests.
-
-DefaultReplicas:
-
-    Default number of replicas to write if not specified by the
-    client. If this is zero or omitted, the site-wide
-    defaultCollectionReplication configuration will be used.
-
-Timeout:
-
-    Timeout for requests to keep services, with units (e.g., "120s",
-    "2m").
-
-PIDFile:
-
-    Path to PID file. During startup this file will be created if
-    needed, and locked using flock() until keepproxy exits. If it is
-    already locked, or any error is encountered while writing to it,
-    keepproxy will exit immediately. If omitted or empty, no PID file
-    will be used.
-
-Debug:
-
-    Enable debug logging.
-
-ManagementToken:
-
-    Authorization token to be included in all health check requests.
-
-`, exampleConfigFile)
-}
index 58bedd2841b41e0a5cf2a6e1a0cc0a8158ab69f0..34a0c2d75221b0466c23870ead2a996b929987da 100755 (executable)
@@ -79,6 +79,10 @@ Clusters:
         ExternalURL: "https://$localip:${services[workbench2-ssl]}"
       SSO:
         ExternalURL: "https://$localip:${services[sso]}"
+      Keepproxy:
+        InternalURLs:
+          "http://localhost:${services[keepproxy]}/": {}
+        ExternalURL: "http://$localip:${services[keepproxy-ssl]}/"
       Websocket:
         ExternalURL: "wss://$localip:${services[websockets-ssl]}/websocket"
         InternalURLs:
index 78b5bcf8e8d430d302b4922c10b241c9469512f9..96457a6e5931a6dffa0378a09333b2c4edd53698 100755 (executable)
@@ -40,4 +40,4 @@ else
     echo $UUID > /var/lib/arvados/keepproxy-uuid
 fi
 
-exec /usr/local/bin/keepproxy -listen=:${services[keepproxy]}
+exec /usr/local/bin/keepproxy