Merge branch '15003-new-config-struct'
authorTom Clegg <tclegg@veritasgenetics.com>
Fri, 31 May 2019 17:54:57 +0000 (13:54 -0400)
committerTom Clegg <tclegg@veritasgenetics.com>
Fri, 31 May 2019 17:54:57 +0000 (13:54 -0400)
refs #15003

Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tclegg@veritasgenetics.com>

38 files changed:
doc/install/install-controller.html.textile.liquid
doc/install/install-dispatch-cloud.html.textile.liquid
lib/config/config.default.yml
lib/config/deprecated.go
lib/config/generated_config.go
lib/config/load.go
lib/config/load_test.go
lib/controller/cmd.go
lib/controller/federation_test.go
lib/controller/handler.go
lib/controller/handler_test.go
lib/controller/server_test.go
lib/dispatchcloud/cmd.go
lib/dispatchcloud/dispatcher.go
lib/dispatchcloud/dispatcher_test.go
lib/dispatchcloud/driver.go
lib/dispatchcloud/worker/pool.go
lib/dispatchcloud/worker/pool_test.go
lib/service/cmd.go
lib/service/cmd_test.go
lib/service/error.go
sdk/go/arvados/config.go
sdk/go/arvados/duration.go
sdk/go/arvados/fs_collection_test.go
sdk/go/arvados/fs_project_test.go
sdk/go/arvados/fs_site_test.go
sdk/go/arvadostest/fixtures.go
sdk/go/arvadostest/stub.go
sdk/go/health/aggregator.go
sdk/go/health/aggregator_test.go
sdk/python/tests/run_test_server.py
services/api/config/arvados_config.rb
services/crunch-run/copier_test.go
services/health/main.go
services/keep-web/cache_test.go
services/keep-web/cadaver_test.go
services/keep-web/handler_test.go
services/keep-web/server_test.go

index 3e94b290d54076e77a12a44097061f6ed935f79f..394aa0fdf7801c074874cbbd500c07b6f5870f5b 100644 (file)
@@ -92,12 +92,13 @@ Create the cluster configuration file @/etc/arvados/config.yml@ using the follow
 <notextile>
 <pre><code>Clusters:
   <span class="userinput">uuid_prefix</span>:
-    NodeProfiles:
-      apiserver:
-        arvados-controller:
-          Listen: ":<span class="userinput">9004</span>" # must match the "upstream controller" section of your Nginx config
+    Services:
+      Controller:
+        InternalURLs:
+          "http://localhost:<span class="userinput">9004</span>": {} # must match the "upstream controller" section of your Nginx config
+      RailsAPI:
         arvados-api-server:
-          Listen: ":<span class="userinput">8000</span>" # must match the "upstream api" section of your Nginx config
+          "http://localhost:<span class="userinput">8000</span>": {} # must match the "upstream api" section of your Nginx config
     PostgreSQL:
       ConnectionPool: 128
       Connection:
index 42c814b8791f018d51ff66622f2d6f8669655578..bc3be8f1d7e88f463d1e954245bec978a3ab967b 100644 (file)
@@ -66,22 +66,20 @@ Add or update the following portions of your cluster configuration file, @/etc/a
   <span class="userinput">uuid_prefix</span>:
     ManagementToken: xyzzy
     SystemRootToken: <span class="userinput">zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz</span>
-    NodeProfiles:
-      # The key "apiserver" corresponds to ARVADOS_NODE_PROFILE in environment file (see below).
-      apiserver:
-        arvados-dispatch-cloud:
-          Listen: ":9006"
     Services:
       Controller:
         ExternalURL: "https://<span class="userinput">uuid_prefix.arvadosapi.com</span>"
-    CloudVMs:
-      # BootProbeCommand is a shell command that succeeds when an instance is ready for service
-      BootProbeCommand: "sudo systemctl status docker"
+      DispatchCloud:
+        InternalURLs:
+          "http://localhost:9006": {}
+    Containers:
+      CloudVMs:
+        # BootProbeCommand is a shell command that succeeds when an instance is ready for service
+        BootProbeCommand: "sudo systemctl status docker"
 
-      <b># --- driver-specific configuration goes here --- see Amazon and Azure examples below ---</b>
+        <b># --- driver-specific configuration goes here --- see Amazon and Azure examples below ---</b>
 
-    Dispatch:
-      PrivateKey: |
+      DispatchPrivateKey: |
         -----BEGIN RSA PRIVATE KEY-----
         MIIEpQIBAAKCAQEAqXoCzcOBkFQ7w4dvXf9B++1ctgZRqEbgRYL3SstuMV4oawks
         ttUuxJycDdsPmeYcHsKo8vsEZpN6iYsX6ZZzhkO5nEayUTU8sBjmg1ZCTo4QqKXr
@@ -113,18 +111,19 @@ Minimal configuration example for Amazon EC2:
 <notextile>
 <pre><code>Clusters:
   <span class="userinput">uuid_prefix</span>:
-    CloudVMs:
-      ImageID: ami-01234567890abcdef
-      Driver: ec2
-      DriverParameters:
-        AccessKeyID: EALMF21BJC7MKNF9FVVR
-        SecretAccessKey: yKJAPmoCQOMtYWzEUQ1tKTyrocTcbH60CRvGP3pM
-        SecurityGroupIDs:
-        - sg-0123abcd
-        SubnetID: subnet-0123abcd
-        Region: us-east-1
-        EBSVolumeType: gp2
-        AdminUsername: debian
+    Containers:
+      CloudVMs:
+        ImageID: ami-01234567890abcdef
+        Driver: ec2
+        DriverParameters:
+          AccessKeyID: EALMF21BJC7MKNF9FVVR
+          SecretAccessKey: yKJAPmoCQOMtYWzEUQ1tKTyrocTcbH60CRvGP3pM
+          SecurityGroupIDs:
+          - sg-0123abcd
+          SubnetID: subnet-0123abcd
+          Region: us-east-1
+          EBSVolumeType: gp2
+          AdminUsername: debian
 </code></pre>
 </notextile>
 
@@ -133,30 +132,24 @@ Minimal configuration example for Azure:
 <notextile>
 <pre><code>Clusters:
   <span class="userinput">uuid_prefix</span>:
-    CloudVMs:
-      ImageID: "https://zzzzzzzz.blob.core.windows.net/system/Microsoft.Compute/Images/images/zzzzz-compute-osDisk.55555555-5555-5555-5555-555555555555.vhd"
-      Driver: azure
-      DriverParameters:
-        SubscriptionID: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
-        ClientID: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
-        ClientSecret: 2WyXt0XFbEtutnf2hp528t6Wk9S5bOHWkRaaWwavKQo=
-        TenantID: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
-        CloudEnvironment: AzurePublicCloud
-        ResourceGroup: zzzzz
-        Location: centralus
-        Network: zzzzz
-        Subnet: zzzzz-subnet-private
-        StorageAccount: example
-        BlobContainer: vhds
-        DeleteDanglingResourcesAfter: 20s
-        AdminUsername: arvados
-</code></pre>
-</notextile>
-
-Create the host configuration file @/etc/arvados/environment@.
-
-<notextile>
-<pre><code>ARVADOS_NODE_PROFILE=apiserver
+    Containers:
+      CloudVMs:
+        ImageID: "https://zzzzzzzz.blob.core.windows.net/system/Microsoft.Compute/Images/images/zzzzz-compute-osDisk.55555555-5555-5555-5555-555555555555.vhd"
+        Driver: azure
+        DriverParameters:
+          SubscriptionID: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
+          ClientID: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
+          ClientSecret: 2WyXt0XFbEtutnf2hp528t6Wk9S5bOHWkRaaWwavKQo=
+          TenantID: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
+          CloudEnvironment: AzurePublicCloud
+          ResourceGroup: zzzzz
+          Location: centralus
+          Network: zzzzz
+          Subnet: zzzzz-subnet-private
+          StorageAccount: example
+          BlobContainer: vhds
+          DeleteDanglingResourcesAfter: 20s
+          AdminUsername: arvados
 </code></pre>
 </notextile>
 
index bd4c3521d17bfeba58d851a42768c290ed229f27..f19238f42adc722010ae2a14f4fa7a70fe507d06 100644 (file)
@@ -21,11 +21,7 @@ Clusters:
     Services:
       RailsAPI:
         InternalURLs: {}
-      GitHTTP:
-        InternalURLs: {}
-        ExternalURL: ""
-      Keepstore:
-        InternalURLs: {}
+        ExternalURL: "-"
       Controller:
         InternalURLs: {}
         ExternalURL: ""
@@ -34,6 +30,7 @@ Clusters:
         ExternalURL: ""
       Keepbalance:
         InternalURLs: {}
+        ExternalURL: "-"
       GitHTTP:
         InternalURLs: {}
         ExternalURL: ""
@@ -41,6 +38,7 @@ Clusters:
         ExternalURL: ""
       DispatchCloud:
         InternalURLs: {}
+        ExternalURL: "-"
       SSO:
         ExternalURL: ""
       Keepproxy:
@@ -54,6 +52,7 @@ Clusters:
         ExternalURL: ""
       Keepstore:
         InternalURLs: {}
+        ExternalURL: "-"
       Composer:
         ExternalURL: ""
       WebShell:
@@ -63,6 +62,13 @@ Clusters:
         ExternalURL: ""
       Workbench2:
         ExternalURL: ""
+      Nodemanager:
+        InternalURLs: {}
+        ExternalURL: "-"
+      Health:
+        InternalURLs: {}
+        ExternalURL: "-"
+
     PostgreSQL:
       # max concurrent connections per arvados server daemon
       ConnectionPool: 32
@@ -118,6 +124,9 @@ Clusters:
       # site secret. It should be at least 50 characters.
       RailsSessionSecretToken: ""
 
+      # Maximum wall clock time to spend handling an incoming request.
+      RequestTimeout: 5m
+
     Users:
       # Config parameters to automatically setup new users.  If enabled,
       # this users will be able to self-activate.  Enable this if you want
@@ -185,6 +194,14 @@ Clusters:
       UnloggedAttributes: []
 
     SystemLogs:
+
+      # Logging threshold: panic, fatal, error, warn, info, debug, or
+      # trace
+      LogLevel: info
+
+      # Logging format: json or text
+      Format: json
+
       # Maximum characters of (JSON-encoded) query parameters to include
       # in each request log entry. When params exceed this size, they will
       # be JSON-encoded, truncated to this size, and logged as
@@ -271,6 +288,8 @@ Clusters:
       Repositories: /var/lib/arvados/git/repositories
 
     TLS:
+      Certificate: ""
+      Key: ""
       Insecure: false
 
     Containers:
@@ -323,6 +342,16 @@ Clusters:
       # troubleshooting purposes.
       LogReuseDecisions: false
 
+      # PEM encoded SSH key (RSA, DSA, or ECDSA) used by the
+      # (experimental) cloud dispatcher for executing containers on
+      # worker VMs. Begins with "-----BEGIN RSA PRIVATE KEY-----\n"
+      # and ends with "\n-----END RSA PRIVATE KEY-----\n".
+      DispatchPrivateKey: none
+
+      # Maximum time to wait for workers to come up before abandoning
+      # stale locks from a previous dispatch process.
+      StaleLockTimeout: 1m
+
       Logging:
         # When you run the db:delete_old_container_logs task, it will find
         # containers that have been finished for at least this many seconds,
@@ -445,6 +474,111 @@ Clusters:
         # original job reuse behavior, and is still the default).
         ReuseJobIfOutputsDiffer: false
 
+      CloudVMs:
+        # Enable the cloud scheduler (experimental).
+        Enable: false
+
+        # Name/number of port where workers' SSH services listen.
+        SSHPort: "22"
+
+        # Interval between queue polls.
+        PollInterval: 10s
+
+        # Shell command to execute on each worker to determine whether
+        # the worker is booted and ready to run containers. It should
+        # exit zero if the worker is ready.
+        BootProbeCommand: "docker ps"
+
+        # Minimum interval between consecutive probes to a single
+        # worker.
+        ProbeInterval: 10s
+
+        # Maximum probes per second, across all workers in a pool.
+        MaxProbesPerSecond: 10
+
+        # Time before repeating SIGTERM when killing a container.
+        TimeoutSignal: 5s
+
+        # Time to give up on SIGTERM and write off the worker.
+        TimeoutTERM: 2m
+
+        # Maximum create/destroy-instance operations per second (0 =
+        # unlimited).
+        MaxCloudOpsPerSecond: 0
+
+        # Interval between cloud provider syncs/updates ("list all
+        # instances").
+        SyncInterval: 1m
+
+        # Time to leave an idle worker running (in case new containers
+        # appear in the queue that it can run) before shutting it
+        # down.
+        TimeoutIdle: 1m
+
+        # Time to wait for a new worker to boot (i.e., pass
+        # BootProbeCommand) before giving up and shutting it down.
+        TimeoutBooting: 10m
+
+        # Maximum time a worker can stay alive with no successful
+        # probes before being automatically shut down.
+        TimeoutProbe: 10m
+
+        # Time after shutting down a worker to retry the
+        # shutdown/destroy operation.
+        TimeoutShutdown: 10s
+
+        # Worker VM image ID.
+        ImageID: ami-01234567890abcdef
+
+        # Cloud driver: "azure" (Microsoft Azure) or "ec2" (Amazon AWS).
+        Driver: ec2
+
+        # Cloud-specific driver parameters.
+        DriverParameters:
+
+          # (ec2) Credentials.
+          AccessKeyID: ""
+          SecretAccessKey: ""
+
+          # (ec2) Instance configuration.
+          SecurityGroupIDs:
+            - ""
+          SubnetID: ""
+          Region: ""
+          EBSVolumeType: gp2
+          AdminUsername: debian
+
+          # (azure) Credentials.
+          SubscriptionID: ""
+          ClientID: ""
+          ClientSecret: ""
+          TenantID: ""
+
+          # (azure) Instance configuration.
+          CloudEnvironment: AzurePublicCloud
+          ResourceGroup: ""
+          Location: centralus
+          Network: ""
+          Subnet: ""
+          StorageAccount: ""
+          BlobContainer: ""
+          DeleteDanglingResourcesAfter: 20s
+          AdminUsername: arvados
+
+    InstanceTypes:
+
+      # Use the instance type name as the key (in place of "SAMPLE" in
+      # this sample entry).
+      SAMPLE:
+        # Cloud provider's instance type. Defaults to the configured type name.
+        ProviderType: ""
+        VCPUs: 1
+        RAM: 128MiB
+        IncludedScratch: 16GB
+        AddedScratch: 0
+        Price: 0.1
+        Preemptible: false
+
     Mail:
       MailchimpAPIKey: ""
       MailchimpListID: ""
@@ -455,5 +589,14 @@ Clusters:
       EmailFrom: ""
     RemoteClusters:
       "*":
+        Host: ""
+        Proxy: false
+        Scheme: https
+        Insecure: false
+        ActivateUsers: false
+      SAMPLE:
+        Host: sample.arvadosapi.com
         Proxy: false
+        Scheme: https
+        Insecure: false
         ActivateUsers: false
index c8f943f3ccafe2b97a54a6edcacdff04d71d60fa..8ffa2a58341e952b67eca0220e59bd423273ab9b 100644 (file)
@@ -20,13 +20,33 @@ type deprRequestLimits struct {
 
 type deprCluster struct {
        RequestLimits deprRequestLimits
-       NodeProfiles  map[string]arvados.NodeProfile
+       NodeProfiles  map[string]nodeProfile
 }
 
 type deprecatedConfig struct {
        Clusters map[string]deprCluster
 }
 
+type nodeProfile struct {
+       Controller    systemServiceInstance `json:"arvados-controller"`
+       Health        systemServiceInstance `json:"arvados-health"`
+       Keepbalance   systemServiceInstance `json:"keep-balance"`
+       Keepproxy     systemServiceInstance `json:"keepproxy"`
+       Keepstore     systemServiceInstance `json:"keepstore"`
+       Keepweb       systemServiceInstance `json:"keep-web"`
+       Nodemanager   systemServiceInstance `json:"arvados-node-manager"`
+       DispatchCloud systemServiceInstance `json:"arvados-dispatch-cloud"`
+       RailsAPI      systemServiceInstance `json:"arvados-api-server"`
+       Websocket     systemServiceInstance `json:"arvados-ws"`
+       Workbench1    systemServiceInstance `json:"arvados-workbench"`
+}
+
+type systemServiceInstance struct {
+       Listen   string
+       TLS      bool
+       Insecure bool
+}
+
 func applyDeprecatedConfig(cfg *arvados.Config, configdata []byte, log logger) error {
        var dc deprecatedConfig
        err := yaml.Unmarshal(configdata, &dc)
@@ -63,7 +83,7 @@ func applyDeprecatedConfig(cfg *arvados.Config, configdata []byte, log logger) e
        return nil
 }
 
-func applyDeprecatedNodeProfile(hostname string, ssi arvados.SystemServiceInstance, svc *arvados.Service) {
+func applyDeprecatedNodeProfile(hostname string, ssi systemServiceInstance, svc *arvados.Service) {
        scheme := "https"
        if !ssi.TLS {
                scheme = "http"
index d51cb9935fce825450d0995a6d51edb16bd55013..59feeec4939cc7bd523034f97cbebf94b02376cb 100644 (file)
@@ -27,11 +27,7 @@ Clusters:
     Services:
       RailsAPI:
         InternalURLs: {}
-      GitHTTP:
-        InternalURLs: {}
-        ExternalURL: ""
-      Keepstore:
-        InternalURLs: {}
+        ExternalURL: "-"
       Controller:
         InternalURLs: {}
         ExternalURL: ""
@@ -40,6 +36,7 @@ Clusters:
         ExternalURL: ""
       Keepbalance:
         InternalURLs: {}
+        ExternalURL: "-"
       GitHTTP:
         InternalURLs: {}
         ExternalURL: ""
@@ -47,6 +44,7 @@ Clusters:
         ExternalURL: ""
       DispatchCloud:
         InternalURLs: {}
+        ExternalURL: "-"
       SSO:
         ExternalURL: ""
       Keepproxy:
@@ -60,6 +58,7 @@ Clusters:
         ExternalURL: ""
       Keepstore:
         InternalURLs: {}
+        ExternalURL: "-"
       Composer:
         ExternalURL: ""
       WebShell:
@@ -69,6 +68,13 @@ Clusters:
         ExternalURL: ""
       Workbench2:
         ExternalURL: ""
+      Nodemanager:
+        InternalURLs: {}
+        ExternalURL: "-"
+      Health:
+        InternalURLs: {}
+        ExternalURL: "-"
+
     PostgreSQL:
       # max concurrent connections per arvados server daemon
       ConnectionPool: 32
@@ -124,6 +130,9 @@ Clusters:
       # site secret. It should be at least 50 characters.
       RailsSessionSecretToken: ""
 
+      # Maximum wall clock time to spend handling an incoming request.
+      RequestTimeout: 5m
+
     Users:
       # Config parameters to automatically setup new users.  If enabled,
       # this users will be able to self-activate.  Enable this if you want
@@ -191,6 +200,14 @@ Clusters:
       UnloggedAttributes: []
 
     SystemLogs:
+
+      # Logging threshold: panic, fatal, error, warn, info, debug, or
+      # trace
+      LogLevel: info
+
+      # Logging format: json or text
+      Format: json
+
       # Maximum characters of (JSON-encoded) query parameters to include
       # in each request log entry. When params exceed this size, they will
       # be JSON-encoded, truncated to this size, and logged as
@@ -277,6 +294,8 @@ Clusters:
       Repositories: /var/lib/arvados/git/repositories
 
     TLS:
+      Certificate: ""
+      Key: ""
       Insecure: false
 
     Containers:
@@ -329,6 +348,16 @@ Clusters:
       # troubleshooting purposes.
       LogReuseDecisions: false
 
+      # PEM encoded SSH key (RSA, DSA, or ECDSA) used by the
+      # (experimental) cloud dispatcher for executing containers on
+      # worker VMs. Begins with "-----BEGIN RSA PRIVATE KEY-----\n"
+      # and ends with "\n-----END RSA PRIVATE KEY-----\n".
+      DispatchPrivateKey: none
+
+      # Maximum time to wait for workers to come up before abandoning
+      # stale locks from a previous dispatch process.
+      StaleLockTimeout: 1m
+
       Logging:
         # When you run the db:delete_old_container_logs task, it will find
         # containers that have been finished for at least this many seconds,
@@ -451,6 +480,111 @@ Clusters:
         # original job reuse behavior, and is still the default).
         ReuseJobIfOutputsDiffer: false
 
+      CloudVMs:
+        # Enable the cloud scheduler (experimental).
+        Enable: false
+
+        # Name/number of port where workers' SSH services listen.
+        SSHPort: "22"
+
+        # Interval between queue polls.
+        PollInterval: 10s
+
+        # Shell command to execute on each worker to determine whether
+        # the worker is booted and ready to run containers. It should
+        # exit zero if the worker is ready.
+        BootProbeCommand: "docker ps"
+
+        # Minimum interval between consecutive probes to a single
+        # worker.
+        ProbeInterval: 10s
+
+        # Maximum probes per second, across all workers in a pool.
+        MaxProbesPerSecond: 10
+
+        # Time before repeating SIGTERM when killing a container.
+        TimeoutSignal: 5s
+
+        # Time to give up on SIGTERM and write off the worker.
+        TimeoutTERM: 2m
+
+        # Maximum create/destroy-instance operations per second (0 =
+        # unlimited).
+        MaxCloudOpsPerSecond: 0
+
+        # Interval between cloud provider syncs/updates ("list all
+        # instances").
+        SyncInterval: 1m
+
+        # Time to leave an idle worker running (in case new containers
+        # appear in the queue that it can run) before shutting it
+        # down.
+        TimeoutIdle: 1m
+
+        # Time to wait for a new worker to boot (i.e., pass
+        # BootProbeCommand) before giving up and shutting it down.
+        TimeoutBooting: 10m
+
+        # Maximum time a worker can stay alive with no successful
+        # probes before being automatically shut down.
+        TimeoutProbe: 10m
+
+        # Time after shutting down a worker to retry the
+        # shutdown/destroy operation.
+        TimeoutShutdown: 10s
+
+        # Worker VM image ID.
+        ImageID: ami-01234567890abcdef
+
+        # Cloud driver: "azure" (Microsoft Azure) or "ec2" (Amazon AWS).
+        Driver: ec2
+
+        # Cloud-specific driver parameters.
+        DriverParameters:
+
+          # (ec2) Credentials.
+          AccessKeyID: ""
+          SecretAccessKey: ""
+
+          # (ec2) Instance configuration.
+          SecurityGroupIDs:
+            - ""
+          SubnetID: ""
+          Region: ""
+          EBSVolumeType: gp2
+          AdminUsername: debian
+
+          # (azure) Credentials.
+          SubscriptionID: ""
+          ClientID: ""
+          ClientSecret: ""
+          TenantID: ""
+
+          # (azure) Instance configuration.
+          CloudEnvironment: AzurePublicCloud
+          ResourceGroup: ""
+          Location: centralus
+          Network: ""
+          Subnet: ""
+          StorageAccount: ""
+          BlobContainer: ""
+          DeleteDanglingResourcesAfter: 20s
+          AdminUsername: arvados
+
+    InstanceTypes:
+
+      # Use the instance type name as the key (in place of "SAMPLE" in
+      # this sample entry).
+      SAMPLE:
+        # Cloud provider's instance type. Defaults to the configured type name.
+        ProviderType: ""
+        VCPUs: 1
+        RAM: 128MiB
+        IncludedScratch: 16GB
+        AddedScratch: 0
+        Price: 0.1
+        Preemptible: false
+
     Mail:
       MailchimpAPIKey: ""
       MailchimpListID: ""
@@ -461,6 +595,15 @@ Clusters:
       EmailFrom: ""
     RemoteClusters:
       "*":
+        Host: ""
+        Proxy: false
+        Scheme: https
+        Insecure: false
+        ActivateUsers: false
+      SAMPLE:
+        Host: sample.arvadosapi.com
         Proxy: false
+        Scheme: https
+        Insecure: false
         ActivateUsers: false
 `)
index 526a050fbbdf923a076faab9ad4ce9b7954a1487..3ed2b9928f6ef734cf9f739250e0d891d1664727 100644 (file)
@@ -79,6 +79,7 @@ func load(rdr io.Reader, log logger, useDeprecated bool) (*arvados.Config, error
                return nil, fmt.Errorf("loading config data: %s", err)
        }
        logExtraKeys(log, merged, src, "")
+       removeSampleKeys(merged)
        err = mergo.Merge(&merged, src, mergo.WithOverride)
        if err != nil {
                return nil, fmt.Errorf("merging config data: %s", err)
@@ -129,14 +130,32 @@ func checkKeyConflict(label string, m map[string]string) error {
        return nil
 }
 
+func removeSampleKeys(m map[string]interface{}) {
+       delete(m, "SAMPLE")
+       for _, v := range m {
+               if v, _ := v.(map[string]interface{}); v != nil {
+                       removeSampleKeys(v)
+               }
+       }
+}
+
 func logExtraKeys(log logger, expected, supplied map[string]interface{}, prefix string) {
        if log == nil {
                return
        }
+       allowed := map[string]interface{}{}
+       for k, v := range expected {
+               allowed[strings.ToLower(k)] = v
+       }
        for k, vsupp := range supplied {
-               if vexp, ok := expected[k]; !ok {
+               vexp, ok := allowed[strings.ToLower(k)]
+               if !ok && expected["SAMPLE"] != nil {
+                       vexp = expected["SAMPLE"]
+               } else if !ok {
                        log.Warnf("deprecated or unknown config entry: %s%s", prefix, k)
-               } else if vsupp, ok := vsupp.(map[string]interface{}); !ok {
+                       continue
+               }
+               if vsupp, ok := vsupp.(map[string]interface{}); !ok {
                        // if vsupp is a map but vexp isn't map, this
                        // will be caught elsewhere; see TestBadType.
                        continue
index d72c762b4846ce99ba327a4192d4fadd40f80802..6ce81bb5f9826b2374d9b7e77de9f1468e9e28c5 100644 (file)
@@ -9,10 +9,13 @@ import (
        "io"
        "os"
        "os/exec"
+       "strings"
        "testing"
 
+       "git.curoverse.com/arvados.git/sdk/go/arvados"
        "git.curoverse.com/arvados.git/sdk/go/ctxlog"
        "github.com/ghodss/yaml"
+       "github.com/sirupsen/logrus"
        check "gopkg.in/check.v1"
 )
 
@@ -42,6 +45,23 @@ func (s *LoadSuite) TestNoConfigs(c *check.C) {
        c.Check(cc.API.MaxItemsPerResponse, check.Equals, 1000)
 }
 
+func (s *LoadSuite) TestSampleKeys(c *check.C) {
+       for _, yaml := range []string{
+               `{"Clusters":{"z1111":{}}}`,
+               `{"Clusters":{"z1111":{"InstanceTypes":{"Foo":{"RAM": "12345M"}}}}}`,
+       } {
+               cfg, err := Load(bytes.NewBufferString(yaml), ctxlog.TestLogger(c))
+               c.Assert(err, check.IsNil)
+               cc, err := cfg.GetCluster("z1111")
+               _, hasSample := cc.InstanceTypes["SAMPLE"]
+               c.Check(hasSample, check.Equals, false)
+               if strings.Contains(yaml, "Foo") {
+                       c.Check(cc.InstanceTypes["Foo"].RAM, check.Equals, arvados.ByteSize(12345000000))
+                       c.Check(cc.InstanceTypes["Foo"].Price, check.Equals, 0.0)
+               }
+       }
+}
+
 func (s *LoadSuite) TestMultipleClusters(c *check.C) {
        cfg, err := Load(bytes.NewBufferString(`{"Clusters":{"z1111":{},"z2222":{}}}`), ctxlog.TestLogger(c))
        c.Assert(err, check.IsNil)
@@ -53,6 +73,44 @@ func (s *LoadSuite) TestMultipleClusters(c *check.C) {
        c.Check(c2.ClusterID, check.Equals, "z2222")
 }
 
+func (s *LoadSuite) TestDeprecatedOrUnknownWarning(c *check.C) {
+       var logbuf bytes.Buffer
+       logger := logrus.New()
+       logger.Out = &logbuf
+       _, err := Load(bytes.NewBufferString(`
+Clusters:
+  zzzzz:
+    postgresql: {}
+    BadKey: {}
+    Containers: {}
+    RemoteClusters:
+      z2222:
+        Host: z2222.arvadosapi.com
+        Proxy: true
+        BadKey: badValue
+`), logger)
+       c.Assert(err, check.IsNil)
+       logs := strings.Split(strings.TrimSuffix(logbuf.String(), "\n"), "\n")
+       for _, log := range logs {
+               c.Check(log, check.Matches, `.*deprecated or unknown config entry:.*BadKey.*`)
+       }
+       c.Check(logs, check.HasLen, 2)
+}
+
+func (s *LoadSuite) TestNoWarningsForDumpedConfig(c *check.C) {
+       var logbuf bytes.Buffer
+       logger := logrus.New()
+       logger.Out = &logbuf
+       cfg, err := Load(bytes.NewBufferString(`{"Clusters":{"zzzzz":{}}}`), logger)
+       c.Assert(err, check.IsNil)
+       yaml, err := yaml.Marshal(cfg)
+       c.Assert(err, check.IsNil)
+       cfgDumped, err := Load(bytes.NewBuffer(yaml), logger)
+       c.Assert(err, check.IsNil)
+       c.Check(cfg, check.DeepEquals, cfgDumped)
+       c.Check(logbuf.String(), check.Equals, "")
+}
+
 func (s *LoadSuite) TestPostgreSQLKeyConflict(c *check.C) {
        _, err := Load(bytes.NewBufferString(`
 Clusters:
index f0268091bedb58f412d4e93ba675481d99f5e3ef..4345370469d07f3d5be685b9dd2e4a0efbe1ab7b 100644 (file)
@@ -14,6 +14,6 @@ import (
 
 var Command cmd.Handler = service.Command(arvados.ServiceNameController, newHandler)
 
-func newHandler(_ context.Context, cluster *arvados.Cluster, np *arvados.NodeProfile, _ string) service.Handler {
-       return &Handler{Cluster: cluster, NodeProfile: np}
+func newHandler(_ context.Context, cluster *arvados.Cluster, _ string) service.Handler {
+       return &Handler{Cluster: cluster}
 }
index c4aa33c15e724feb807b7ac35f3a9d0312a62770..1c859cfc515d142a0289610e402e725e07bfebb1 100644 (file)
@@ -54,25 +54,22 @@ func (s *FederationSuite) SetUpTest(c *check.C) {
        s.remoteMock.Server.Handler = http.HandlerFunc(s.remoteMockHandler)
        c.Assert(s.remoteMock.Start(), check.IsNil)
 
-       nodeProfile := arvados.NodeProfile{
-               Controller: arvados.SystemServiceInstance{Listen: ":"},
-               RailsAPI:   arvados.SystemServiceInstance{Listen: ":1"}, // local reqs will error "connection refused"
-       }
-       s.testHandler = &Handler{Cluster: &arvados.Cluster{
+       cluster := &arvados.Cluster{
                ClusterID:  "zhome",
                PostgreSQL: integrationTestCluster().PostgreSQL,
-               NodeProfiles: map[string]arvados.NodeProfile{
-                       "*": nodeProfile,
-               },
+               TLS:        arvados.TLS{Insecure: true},
                API: arvados.API{
                        MaxItemsPerResponse:     1000,
                        MaxRequestAmplification: 4,
                },
-       }, NodeProfile: &nodeProfile}
+       }
+       arvadostest.SetServiceURL(&cluster.Services.RailsAPI, "http://localhost:1/")
+       arvadostest.SetServiceURL(&cluster.Services.Controller, "http://localhost:/")
+       s.testHandler = &Handler{Cluster: cluster}
        s.testServer = newServerFromIntegrationTestEnv(c)
        s.testServer.Server.Handler = httpserver.AddRequestIDs(httpserver.LogRequests(s.log, s.testHandler))
 
-       s.testHandler.Cluster.RemoteClusters = map[string]arvados.RemoteCluster{
+       cluster.RemoteClusters = map[string]arvados.RemoteCluster{
                "zzzzz": {
                        Host:   s.remoteServer.Addr,
                        Proxy:  true,
@@ -318,16 +315,8 @@ func (s *FederationSuite) localServiceHandler(c *check.C, h http.Handler) *https
                        Handler: h,
                },
        }
-
        c.Assert(srv.Start(), check.IsNil)
-
-       np := arvados.NodeProfile{
-               Controller: arvados.SystemServiceInstance{Listen: ":"},
-               RailsAPI: arvados.SystemServiceInstance{Listen: srv.Addr,
-                       TLS: false, Insecure: true}}
-       s.testHandler.Cluster.NodeProfiles["*"] = np
-       s.testHandler.NodeProfile = &np
-
+       arvadostest.SetServiceURL(&s.testHandler.Cluster.Services.RailsAPI, "http://"+srv.Addr)
        return srv
 }
 
@@ -338,13 +327,8 @@ func (s *FederationSuite) localServiceReturns404(c *check.C) *httpserver.Server
 }
 
 func (s *FederationSuite) TestGetLocalCollection(c *check.C) {
-       np := arvados.NodeProfile{
-               Controller: arvados.SystemServiceInstance{Listen: ":"},
-               RailsAPI: arvados.SystemServiceInstance{Listen: os.Getenv("ARVADOS_TEST_API_HOST"),
-                       TLS: true, Insecure: true}}
        s.testHandler.Cluster.ClusterID = "zzzzz"
-       s.testHandler.Cluster.NodeProfiles["*"] = np
-       s.testHandler.NodeProfile = &np
+       arvadostest.SetServiceURL(&s.testHandler.Cluster.Services.RailsAPI, "https://"+os.Getenv("ARVADOS_TEST_API_HOST"))
 
        // HTTP GET
 
@@ -416,12 +400,7 @@ func (s *FederationSuite) TestSignedLocatorPattern(c *check.C) {
 }
 
 func (s *FederationSuite) TestGetLocalCollectionByPDH(c *check.C) {
-       np := arvados.NodeProfile{
-               Controller: arvados.SystemServiceInstance{Listen: ":"},
-               RailsAPI: arvados.SystemServiceInstance{Listen: os.Getenv("ARVADOS_TEST_API_HOST"),
-                       TLS: true, Insecure: true}}
-       s.testHandler.Cluster.NodeProfiles["*"] = np
-       s.testHandler.NodeProfile = &np
+       arvadostest.SetServiceURL(&s.testHandler.Cluster.Services.RailsAPI, "https://"+os.Getenv("ARVADOS_TEST_API_HOST"))
 
        req := httptest.NewRequest("GET", "/arvados/v1/collections/"+arvadostest.UserAgreementPDH, nil)
        req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
@@ -505,12 +484,7 @@ func (s *FederationSuite) TestGetCollectionByPDHErrorBadHash(c *check.C) {
 }
 
 func (s *FederationSuite) TestSaltedTokenGetCollectionByPDH(c *check.C) {
-       np := arvados.NodeProfile{
-               Controller: arvados.SystemServiceInstance{Listen: ":"},
-               RailsAPI: arvados.SystemServiceInstance{Listen: os.Getenv("ARVADOS_TEST_API_HOST"),
-                       TLS: true, Insecure: true}}
-       s.testHandler.Cluster.NodeProfiles["*"] = np
-       s.testHandler.NodeProfile = &np
+       arvadostest.SetServiceURL(&s.testHandler.Cluster.Services.RailsAPI, "https://"+os.Getenv("ARVADOS_TEST_API_HOST"))
 
        req := httptest.NewRequest("GET", "/arvados/v1/collections/"+arvadostest.UserAgreementPDH, nil)
        req.Header.Set("Authorization", "Bearer v2/zzzzz-gj3su-077z32aux8dg2s1/282d7d172b6cfdce364c5ed12ddf7417b2d00065")
@@ -526,12 +500,7 @@ func (s *FederationSuite) TestSaltedTokenGetCollectionByPDH(c *check.C) {
 }
 
 func (s *FederationSuite) TestSaltedTokenGetCollectionByPDHError(c *check.C) {
-       np := arvados.NodeProfile{
-               Controller: arvados.SystemServiceInstance{Listen: ":"},
-               RailsAPI: arvados.SystemServiceInstance{Listen: os.Getenv("ARVADOS_TEST_API_HOST"),
-                       TLS: true, Insecure: true}}
-       s.testHandler.Cluster.NodeProfiles["*"] = np
-       s.testHandler.NodeProfile = &np
+       arvadostest.SetServiceURL(&s.testHandler.Cluster.Services.RailsAPI, "https://"+os.Getenv("ARVADOS_TEST_API_HOST"))
 
        req := httptest.NewRequest("GET", "/arvados/v1/collections/99999999999999999999999999999999+99", nil)
        req.Header.Set("Authorization", "Bearer v2/zzzzz-gj3su-077z32aux8dg2s1/282d7d172b6cfdce364c5ed12ddf7417b2d00065")
@@ -616,13 +585,8 @@ func (s *FederationSuite) TestCreateRemoteContainerRequestCheckRuntimeToken(c *c
        req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveTokenV2)
        req.Header.Set("Content-type", "application/json")
 
-       np := arvados.NodeProfile{
-               Controller: arvados.SystemServiceInstance{Listen: ":"},
-               RailsAPI: arvados.SystemServiceInstance{Listen: os.Getenv("ARVADOS_TEST_API_HOST"),
-                       TLS: true, Insecure: true}}
+       arvadostest.SetServiceURL(&s.testHandler.Cluster.Services.RailsAPI, "https://"+os.Getenv("ARVADOS_TEST_API_HOST"))
        s.testHandler.Cluster.ClusterID = "zzzzz"
-       s.testHandler.Cluster.NodeProfiles["*"] = np
-       s.testHandler.NodeProfile = &np
 
        resp := s.testRequest(req)
        c.Check(resp.StatusCode, check.Equals, http.StatusOK)
index 775d2903475d6ad83eb368b77191cf479065cb57..2c3ce1d4f28d189e956cd3e120b8433214861619 100644 (file)
@@ -8,7 +8,7 @@ import (
        "context"
        "database/sql"
        "errors"
-       "net"
+       "fmt"
        "net/http"
        "net/url"
        "strings"
@@ -22,8 +22,7 @@ import (
 )
 
 type Handler struct {
-       Cluster     *arvados.Cluster
-       NodeProfile *arvados.NodeProfile
+       Cluster *arvados.Cluster
 
        setupOnce      sync.Once
        handlerStack   http.Handler
@@ -50,8 +49,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
                        req.URL.Path = strings.Replace(req.URL.Path, "//", "/", -1)
                }
        }
-       if h.Cluster.HTTPRequestTimeout > 0 {
-               ctx, cancel := context.WithDeadline(req.Context(), time.Now().Add(time.Duration(h.Cluster.HTTPRequestTimeout)))
+       if h.Cluster.API.RequestTimeout > 0 {
+               ctx, cancel := context.WithDeadline(req.Context(), time.Now().Add(time.Duration(h.Cluster.API.RequestTimeout)))
                req = req.WithContext(ctx)
                defer cancel()
        }
@@ -61,7 +60,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 
 func (h *Handler) CheckHealth() error {
        h.setupOnce.Do(h.setup)
-       _, _, err := findRailsAPI(h.Cluster, h.NodeProfile)
+       _, _, err := findRailsAPI(h.Cluster)
        return err
 }
 
@@ -127,7 +126,7 @@ func prepend(next http.Handler, middleware middlewareFunc) http.Handler {
 }
 
 func (h *Handler) localClusterRequest(req *http.Request) (*http.Response, error) {
-       urlOut, insecure, err := findRailsAPI(h.Cluster, h.NodeProfile)
+       urlOut, insecure, err := findRailsAPI(h.Cluster)
        if err != nil {
                return nil, err
        }
@@ -153,22 +152,19 @@ func (h *Handler) proxyRailsAPI(w http.ResponseWriter, req *http.Request, next h
        }
 }
 
-// For now, findRailsAPI always uses the rails API running on this
-// node.
-func findRailsAPI(cluster *arvados.Cluster, np *arvados.NodeProfile) (*url.URL, bool, error) {
-       hostport := np.RailsAPI.Listen
-       if len(hostport) > 1 && hostport[0] == ':' && strings.TrimRight(hostport[1:], "0123456789") == "" {
-               // ":12345" => connect to indicated port on localhost
-               hostport = "localhost" + hostport
-       } else if _, _, err := net.SplitHostPort(hostport); err == nil {
-               // "[::1]:12345" => connect to indicated address & port
-       } else {
-               return nil, false, err
+// Use a localhost entry from Services.RailsAPI.InternalURLs if one is
+// present, otherwise choose an arbitrary entry.
+func findRailsAPI(cluster *arvados.Cluster) (*url.URL, bool, error) {
+       var best *url.URL
+       for target := range cluster.Services.RailsAPI.InternalURLs {
+               target := url.URL(target)
+               best = &target
+               if strings.HasPrefix(target.Host, "localhost:") || strings.HasPrefix(target.Host, "127.0.0.1:") || strings.HasPrefix(target.Host, "[::1]:") {
+                       break
+               }
        }
-       proto := "http"
-       if np.RailsAPI.TLS {
-               proto = "https"
+       if best == nil {
+               return nil, false, fmt.Errorf("Services.RailsAPI.InternalURLs is empty")
        }
-       url, err := url.Parse(proto + "://" + hostport)
-       return url, np.RailsAPI.Insecure, err
+       return best, cluster.TLS.Insecure, nil
 }
index 96110ea85859b05b362f849475a9d77c91919752..a1efaacddff5b2b7c52ad8fd78eb79c0500b2be8 100644 (file)
@@ -42,15 +42,11 @@ func (s *HandlerSuite) SetUpTest(c *check.C) {
        s.cluster = &arvados.Cluster{
                ClusterID:  "zzzzz",
                PostgreSQL: integrationTestCluster().PostgreSQL,
-               NodeProfiles: map[string]arvados.NodeProfile{
-                       "*": {
-                               Controller: arvados.SystemServiceInstance{Listen: ":"},
-                               RailsAPI:   arvados.SystemServiceInstance{Listen: os.Getenv("ARVADOS_TEST_API_HOST"), TLS: true, Insecure: true},
-                       },
-               },
+               TLS:        arvados.TLS{Insecure: true},
        }
-       node := s.cluster.NodeProfiles["*"]
-       s.handler = newHandler(s.ctx, s.cluster, &node, "")
+       arvadostest.SetServiceURL(&s.cluster.Services.RailsAPI, "https://"+os.Getenv("ARVADOS_TEST_API_HOST"))
+       arvadostest.SetServiceURL(&s.cluster.Services.Controller, "http://localhost:/")
+       s.handler = newHandler(s.ctx, s.cluster, "")
 }
 
 func (s *HandlerSuite) TearDownTest(c *check.C) {
@@ -72,7 +68,7 @@ func (s *HandlerSuite) TestProxyDiscoveryDoc(c *check.C) {
 }
 
 func (s *HandlerSuite) TestRequestTimeout(c *check.C) {
-       s.cluster.HTTPRequestTimeout = arvados.Duration(time.Nanosecond)
+       s.cluster.API.RequestTimeout = arvados.Duration(time.Nanosecond)
        req := httptest.NewRequest("GET", "/discovery/v1/apis/arvados/v1/rest", nil)
        resp := httptest.NewRecorder()
        s.handler.ServeHTTP(resp, req)
index ae89c3d7ea4d073fa44885f193af138f81b85508..a398af97b21884ae896f675b1c2ab00a59ae55d4 100644 (file)
@@ -10,6 +10,7 @@ import (
        "path/filepath"
 
        "git.curoverse.com/arvados.git/sdk/go/arvados"
+       "git.curoverse.com/arvados.git/sdk/go/arvadostest"
        "git.curoverse.com/arvados.git/sdk/go/ctxlog"
        "git.curoverse.com/arvados.git/sdk/go/httpserver"
        check "gopkg.in/check.v1"
@@ -32,23 +33,19 @@ func integrationTestCluster() *arvados.Cluster {
 func newServerFromIntegrationTestEnv(c *check.C) *httpserver.Server {
        log := ctxlog.TestLogger(c)
 
-       nodeProfile := arvados.NodeProfile{
-               Controller: arvados.SystemServiceInstance{Listen: ":"},
-               RailsAPI:   arvados.SystemServiceInstance{Listen: os.Getenv("ARVADOS_TEST_API_HOST"), TLS: true, Insecure: true},
-       }
        handler := &Handler{Cluster: &arvados.Cluster{
                ClusterID:  "zzzzz",
                PostgreSQL: integrationTestCluster().PostgreSQL,
-               NodeProfiles: map[string]arvados.NodeProfile{
-                       "*": nodeProfile,
-               },
-       }, NodeProfile: &nodeProfile}
+               TLS:        arvados.TLS{Insecure: true},
+       }}
+       arvadostest.SetServiceURL(&handler.Cluster.Services.RailsAPI, "https://"+os.Getenv("ARVADOS_TEST_API_HOST"))
+       arvadostest.SetServiceURL(&handler.Cluster.Services.Controller, "http://localhost:/")
 
        srv := &httpserver.Server{
                Server: http.Server{
                        Handler: httpserver.AddRequestIDs(httpserver.LogRequests(log, handler)),
                },
-               Addr: nodeProfile.Controller.Listen,
+               Addr: ":",
        }
        return srv
 }
index 22ceb8aebe787ae79c1274cc0c714bc39df04640..ae6ac70e9665f777069232d49eaf0dd76a66d1a2 100644 (file)
@@ -15,10 +15,10 @@ import (
 
 var Command cmd.Handler = service.Command(arvados.ServiceNameDispatchCloud, newHandler)
 
-func newHandler(ctx context.Context, cluster *arvados.Cluster, np *arvados.NodeProfile, token string) service.Handler {
+func newHandler(ctx context.Context, cluster *arvados.Cluster, token string) service.Handler {
        ac, err := arvados.NewClientFromConfig(cluster)
        if err != nil {
-               return service.ErrorHandler(ctx, cluster, np, fmt.Errorf("error initializing client from cluster config: %s", err))
+               return service.ErrorHandler(ctx, cluster, fmt.Errorf("error initializing client from cluster config: %s", err))
        }
        d := &dispatcher{
                Cluster:   cluster,
index 71ff9c784e958fa7927cb3ca57214593d74eecd7..3bf0ee9bd558eeecc27919388874af786baca6c9 100644 (file)
@@ -95,7 +95,7 @@ func (disp *dispatcher) Close() {
 // Make a worker.Executor for the given instance.
 func (disp *dispatcher) newExecutor(inst cloud.Instance) worker.Executor {
        exr := ssh_executor.New(inst)
-       exr.SetTargetPort(disp.Cluster.CloudVMs.SSHPort)
+       exr.SetTargetPort(disp.Cluster.Containers.CloudVMs.SSHPort)
        exr.SetSigners(disp.sshKey)
        return exr
 }
@@ -126,8 +126,8 @@ func (disp *dispatcher) initialize() {
        disp.stop = make(chan struct{}, 1)
        disp.stopped = make(chan struct{})
 
-       if key, err := ssh.ParsePrivateKey([]byte(disp.Cluster.Dispatch.PrivateKey)); err != nil {
-               disp.logger.Fatalf("error parsing configured Dispatch.PrivateKey: %s", err)
+       if key, err := ssh.ParsePrivateKey([]byte(disp.Cluster.Containers.DispatchPrivateKey)); err != nil {
+               disp.logger.Fatalf("error parsing configured Containers.DispatchPrivateKey: %s", err)
        } else {
                disp.sshKey = key
        }
@@ -167,11 +167,11 @@ func (disp *dispatcher) run() {
        defer disp.instanceSet.Stop()
        defer disp.pool.Stop()
 
-       staleLockTimeout := time.Duration(disp.Cluster.Dispatch.StaleLockTimeout)
+       staleLockTimeout := time.Duration(disp.Cluster.Containers.StaleLockTimeout)
        if staleLockTimeout == 0 {
                staleLockTimeout = defaultStaleLockTimeout
        }
-       pollInterval := time.Duration(disp.Cluster.Dispatch.PollInterval)
+       pollInterval := time.Duration(disp.Cluster.Containers.CloudVMs.PollInterval)
        if pollInterval <= 0 {
                pollInterval = defaultPollInterval
        }
index 00157b75c649226880898c802973e9cd03a82173..6b8620ade3d3dbf4f36a1230cb17ae12426c31c5 100644 (file)
@@ -49,23 +49,23 @@ func (s *DispatcherSuite) SetUpTest(c *check.C) {
        }
 
        s.cluster = &arvados.Cluster{
-               CloudVMs: arvados.CloudVMs{
-                       Driver:               "test",
-                       SyncInterval:         arvados.Duration(10 * time.Millisecond),
-                       TimeoutIdle:          arvados.Duration(150 * time.Millisecond),
-                       TimeoutBooting:       arvados.Duration(150 * time.Millisecond),
-                       TimeoutProbe:         arvados.Duration(15 * time.Millisecond),
-                       TimeoutShutdown:      arvados.Duration(5 * time.Millisecond),
-                       MaxCloudOpsPerSecond: 500,
-               },
-               Dispatch: arvados.Dispatch{
-                       PrivateKey:         string(dispatchprivraw),
-                       PollInterval:       arvados.Duration(5 * time.Millisecond),
-                       ProbeInterval:      arvados.Duration(5 * time.Millisecond),
+               Containers: arvados.ContainersConfig{
+                       DispatchPrivateKey: string(dispatchprivraw),
                        StaleLockTimeout:   arvados.Duration(5 * time.Millisecond),
-                       MaxProbesPerSecond: 1000,
-                       TimeoutSignal:      arvados.Duration(3 * time.Millisecond),
-                       TimeoutTERM:        arvados.Duration(20 * time.Millisecond),
+                       CloudVMs: arvados.CloudVMsConfig{
+                               Driver:               "test",
+                               SyncInterval:         arvados.Duration(10 * time.Millisecond),
+                               TimeoutIdle:          arvados.Duration(150 * time.Millisecond),
+                               TimeoutBooting:       arvados.Duration(150 * time.Millisecond),
+                               TimeoutProbe:         arvados.Duration(15 * time.Millisecond),
+                               TimeoutShutdown:      arvados.Duration(5 * time.Millisecond),
+                               MaxCloudOpsPerSecond: 500,
+                               PollInterval:         arvados.Duration(5 * time.Millisecond),
+                               ProbeInterval:        arvados.Duration(5 * time.Millisecond),
+                               MaxProbesPerSecond:   1000,
+                               TimeoutSignal:        arvados.Duration(3 * time.Millisecond),
+                               TimeoutTERM:          arvados.Duration(20 * time.Millisecond),
+                       },
                },
                InstanceTypes: arvados.InstanceTypeMap{
                        test.InstanceType(1).Name:  test.InstanceType(1),
@@ -76,16 +76,9 @@ func (s *DispatcherSuite) SetUpTest(c *check.C) {
                        test.InstanceType(8).Name:  test.InstanceType(8),
                        test.InstanceType(16).Name: test.InstanceType(16),
                },
-               NodeProfiles: map[string]arvados.NodeProfile{
-                       "*": {
-                               Controller:    arvados.SystemServiceInstance{Listen: os.Getenv("ARVADOS_API_HOST")},
-                               DispatchCloud: arvados.SystemServiceInstance{Listen: ":"},
-                       },
-               },
-               Services: arvados.Services{
-                       Controller: arvados.Service{ExternalURL: arvados.URL{Scheme: "https", Host: os.Getenv("ARVADOS_API_HOST")}},
-               },
        }
+       arvadostest.SetServiceURL(&s.cluster.Services.DispatchCloud, "http://localhost:/")
+       arvadostest.SetServiceURL(&s.cluster.Services.Controller, "https://"+os.Getenv("ARVADOS_API_HOST")+"/")
 
        arvClient, err := arvados.NewClientFromConfig(s.cluster)
        c.Check(err, check.IsNil)
@@ -242,7 +235,7 @@ func (s *DispatcherSuite) TestAPIDisabled(c *check.C) {
 
 func (s *DispatcherSuite) TestInstancesAPI(c *check.C) {
        s.cluster.ManagementToken = "abcdefgh"
-       s.cluster.CloudVMs.TimeoutBooting = arvados.Duration(time.Second)
+       s.cluster.Containers.CloudVMs.TimeoutBooting = arvados.Duration(time.Second)
        drivers["test"] = s.stubDriver
        s.disp.setupOnce.Do(s.disp.initialize)
        s.disp.queue = &test.Queue{}
index eb1e48737c8b131cbb919ca71e8f6bbc377c553a..5ec0f73e75bc035a3f2dad725c028e8c5b8502a8 100644 (file)
@@ -22,12 +22,12 @@ var drivers = map[string]cloud.Driver{
 }
 
 func newInstanceSet(cluster *arvados.Cluster, setID cloud.InstanceSetID, logger logrus.FieldLogger) (cloud.InstanceSet, error) {
-       driver, ok := drivers[cluster.CloudVMs.Driver]
+       driver, ok := drivers[cluster.Containers.CloudVMs.Driver]
        if !ok {
-               return nil, fmt.Errorf("unsupported cloud driver %q", cluster.CloudVMs.Driver)
+               return nil, fmt.Errorf("unsupported cloud driver %q", cluster.Containers.CloudVMs.Driver)
        }
-       is, err := driver.InstanceSet(cluster.CloudVMs.DriverParameters, setID, logger)
-       if maxops := cluster.CloudVMs.MaxCloudOpsPerSecond; maxops > 0 {
+       is, err := driver.InstanceSet(cluster.Containers.CloudVMs.DriverParameters, setID, logger)
+       if maxops := cluster.Containers.CloudVMs.MaxCloudOpsPerSecond; maxops > 0 {
                is = &rateLimitedInstanceSet{
                        InstanceSet: is,
                        ticker:      time.NewTicker(time.Second / time.Duration(maxops)),
index 014ab93bfe9c7289bcd99286379a3a26bbc38b18..84b61fc006c239e3cc67e9a7828e6ebe29b6546e 100644 (file)
@@ -97,18 +97,18 @@ func NewPool(logger logrus.FieldLogger, arvClient *arvados.Client, reg *promethe
                arvClient:          arvClient,
                instanceSet:        &throttledInstanceSet{InstanceSet: instanceSet},
                newExecutor:        newExecutor,
-               bootProbeCommand:   cluster.CloudVMs.BootProbeCommand,
-               imageID:            cloud.ImageID(cluster.CloudVMs.ImageID),
+               bootProbeCommand:   cluster.Containers.CloudVMs.BootProbeCommand,
+               imageID:            cloud.ImageID(cluster.Containers.CloudVMs.ImageID),
                instanceTypes:      cluster.InstanceTypes,
-               maxProbesPerSecond: cluster.Dispatch.MaxProbesPerSecond,
-               probeInterval:      duration(cluster.Dispatch.ProbeInterval, defaultProbeInterval),
-               syncInterval:       duration(cluster.CloudVMs.SyncInterval, defaultSyncInterval),
-               timeoutIdle:        duration(cluster.CloudVMs.TimeoutIdle, defaultTimeoutIdle),
-               timeoutBooting:     duration(cluster.CloudVMs.TimeoutBooting, defaultTimeoutBooting),
-               timeoutProbe:       duration(cluster.CloudVMs.TimeoutProbe, defaultTimeoutProbe),
-               timeoutShutdown:    duration(cluster.CloudVMs.TimeoutShutdown, defaultTimeoutShutdown),
-               timeoutTERM:        duration(cluster.Dispatch.TimeoutTERM, defaultTimeoutTERM),
-               timeoutSignal:      duration(cluster.Dispatch.TimeoutSignal, defaultTimeoutSignal),
+               maxProbesPerSecond: cluster.Containers.CloudVMs.MaxProbesPerSecond,
+               probeInterval:      duration(cluster.Containers.CloudVMs.ProbeInterval, defaultProbeInterval),
+               syncInterval:       duration(cluster.Containers.CloudVMs.SyncInterval, defaultSyncInterval),
+               timeoutIdle:        duration(cluster.Containers.CloudVMs.TimeoutIdle, defaultTimeoutIdle),
+               timeoutBooting:     duration(cluster.Containers.CloudVMs.TimeoutBooting, defaultTimeoutBooting),
+               timeoutProbe:       duration(cluster.Containers.CloudVMs.TimeoutProbe, defaultTimeoutProbe),
+               timeoutShutdown:    duration(cluster.Containers.CloudVMs.TimeoutShutdown, defaultTimeoutShutdown),
+               timeoutTERM:        duration(cluster.Containers.CloudVMs.TimeoutTERM, defaultTimeoutTERM),
+               timeoutSignal:      duration(cluster.Containers.CloudVMs.TimeoutSignal, defaultTimeoutSignal),
                installPublicKey:   installPublicKey,
                stop:               make(chan bool),
        }
index fc33a7ab235d7a733903903219302a81c8fc44d0..69395366895d3eb515c45705032dd722894f083e 100644 (file)
@@ -76,13 +76,13 @@ func (suite *PoolSuite) TestResumeAfterRestart(c *check.C) {
        }
 
        cluster := &arvados.Cluster{
-               Dispatch: arvados.Dispatch{
-                       MaxProbesPerSecond: 1000,
-                       ProbeInterval:      arvados.Duration(time.Millisecond * 10),
-               },
-               CloudVMs: arvados.CloudVMs{
-                       BootProbeCommand: "true",
-                       SyncInterval:     arvados.Duration(time.Millisecond * 10),
+               Containers: arvados.ContainersConfig{
+                       CloudVMs: arvados.CloudVMsConfig{
+                               BootProbeCommand:   "true",
+                               MaxProbesPerSecond: 1000,
+                               ProbeInterval:      arvados.Duration(time.Millisecond * 10),
+                               SyncInterval:       arvados.Duration(time.Millisecond * 10),
+                       },
                },
                InstanceTypes: arvados.InstanceTypeMap{
                        type1.Name: type1,
index 955b21f9ea0ef1ac21bc069f67079a767aabac1e..94021163e469fd87c6eb58dc29041ba00b95b65a 100644 (file)
@@ -11,9 +11,11 @@ import (
        "fmt"
        "io"
        "io/ioutil"
+       "net"
        "net/http"
        "net/url"
        "os"
+       "strings"
 
        "git.curoverse.com/arvados.git/lib/cmd"
        "git.curoverse.com/arvados.git/lib/config"
@@ -29,7 +31,7 @@ type Handler interface {
        CheckHealth() error
 }
 
-type NewHandlerFunc func(_ context.Context, _ *arvados.Cluster, _ *arvados.NodeProfile, token string) Handler
+type NewHandlerFunc func(_ context.Context, _ *arvados.Cluster, token string) Handler
 
 type command struct {
        newHandler NewHandlerFunc
@@ -63,7 +65,6 @@ func (c *command) RunCommand(prog string, args []string, stdin io.Reader, stdout
        flags := flag.NewFlagSet("", flag.ContinueOnError)
        flags.SetOutput(stderr)
        configFile := flags.String("config", arvados.DefaultConfigFile, "Site configuration `file`")
-       nodeProfile := flags.String("node-profile", "", "`Name` of NodeProfiles config entry to use (if blank, use $ARVADOS_NODE_PROFILE or hostname reported by OS)")
        err = flags.Parse(args)
        if err == flag.ErrHelp {
                err = nil
@@ -82,24 +83,15 @@ func (c *command) RunCommand(prog string, args []string, stdin io.Reader, stdout
        if err != nil {
                return 1
        }
-       log = ctxlog.New(stderr, cluster.Logging.Format, cluster.Logging.Level).WithFields(logrus.Fields{
+       log = ctxlog.New(stderr, cluster.SystemLogs.Format, cluster.SystemLogs.LogLevel).WithFields(logrus.Fields{
                "PID": os.Getpid(),
        })
        ctx := ctxlog.Context(c.ctx, log)
 
-       profileName := *nodeProfile
-       if profileName == "" {
-               profileName = os.Getenv("ARVADOS_NODE_PROFILE")
-       }
-       profile, err := cluster.GetNodeProfile(profileName)
+       listen, err := getListenAddr(cluster.Services, c.svcName)
        if err != nil {
                return 1
        }
-       listen := profile.ServicePorts()[c.svcName]
-       if listen == "" {
-               err = fmt.Errorf("configuration does not enable the %s service on this host", c.svcName)
-               return 1
-       }
 
        if cluster.SystemRootToken == "" {
                log.Warn("SystemRootToken missing from cluster config, falling back to ARVADOS_API_TOKEN environment variable")
@@ -118,7 +110,7 @@ func (c *command) RunCommand(prog string, args []string, stdin io.Reader, stdout
                }
        }
 
-       handler := c.newHandler(ctx, cluster, profile, cluster.SystemRootToken)
+       handler := c.newHandler(ctx, cluster, cluster.SystemRootToken)
        if err = handler.CheckHealth(); err != nil {
                return 1
        }
@@ -151,3 +143,21 @@ func (c *command) RunCommand(prog string, args []string, stdin io.Reader, stdout
 }
 
 const rfc3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00"
+
+func getListenAddr(svcs arvados.Services, prog arvados.ServiceName) (string, error) {
+       svc, ok := svcs.Map()[prog]
+       if !ok {
+               return "", fmt.Errorf("unknown service name %q", prog)
+       }
+       for url := range svc.InternalURLs {
+               if strings.HasPrefix(url.Host, "localhost:") {
+                       return url.Host, nil
+               }
+               listener, err := net.Listen("tcp", url.Host)
+               if err == nil {
+                       listener.Close()
+                       return url.Host, nil
+               }
+       }
+       return "", fmt.Errorf("configuration does not enable the %s service on this host", prog)
+}
index 62960dc31cb2b71a2b2aea85db0300f00a44995d..bb7c5c51da01a4074da5b1b80506b2e25fc9a25d 100644 (file)
@@ -38,7 +38,7 @@ func (*Suite) TestCommand(c *check.C) {
        ctx, cancel := context.WithCancel(context.Background())
        defer cancel()
 
-       cmd := Command(arvados.ServiceNameController, func(ctx context.Context, _ *arvados.Cluster, _ *arvados.NodeProfile, token string) Handler {
+       cmd := Command(arvados.ServiceNameController, func(ctx context.Context, _ *arvados.Cluster, token string) Handler {
                c.Check(ctx.Value("foo"), check.Equals, "bar")
                c.Check(token, check.Equals, "abcde")
                return &testHandler{ctx: ctx, healthCheck: healthCheck}
index 8955210913c291341fd27e132b86aad086a2f3d7..1ca5c5f4463b00f4afffbff0507134619f14e57c 100644 (file)
@@ -17,7 +17,7 @@ import (
 // responds 500 to all requests.  ErrorHandler itself logs the given
 // error once, and the handler logs it again for each incoming
 // request.
-func ErrorHandler(ctx context.Context, _ *arvados.Cluster, _ *arvados.NodeProfile, err error) Handler {
+func ErrorHandler(ctx context.Context, _ *arvados.Cluster, err error) Handler {
        logger := ctxlog.FromContext(ctx)
        logger.WithError(err).Error("unhealthy service")
        return errorHandler{err, logger}
index 6b3150c6f0e15d5711f9d5d30fdfe62042f20739..275eb9e1905262fb970f2db5ad6e33ea5ba96757 100644 (file)
@@ -9,7 +9,6 @@ import (
        "errors"
        "fmt"
        "net/url"
-       "os"
 
        "git.curoverse.com/arvados.git/sdk/go/config"
 )
@@ -54,23 +53,21 @@ func (sc *Config) GetCluster(clusterID string) (*Cluster, error) {
 type API struct {
        MaxItemsPerResponse     int
        MaxRequestAmplification int
+       RequestTimeout          Duration
 }
 
 type Cluster struct {
-       ClusterID          string `json:"-"`
-       ManagementToken    string
-       SystemRootToken    string
-       Services           Services
-       NodeProfiles       map[string]NodeProfile
-       InstanceTypes      InstanceTypeMap
-       CloudVMs           CloudVMs
-       Dispatch           Dispatch
-       HTTPRequestTimeout Duration
-       RemoteClusters     map[string]RemoteCluster
-       PostgreSQL         PostgreSQL
-       API                API
-       Logging            Logging
-       TLS                TLS
+       ClusterID       string `json:"-"`
+       ManagementToken string
+       SystemRootToken string
+       Services        Services
+       InstanceTypes   InstanceTypeMap
+       Containers      ContainersConfig
+       RemoteClusters  map[string]RemoteCluster
+       PostgreSQL      PostgreSQL
+       API             API
+       SystemLogs      SystemLogs
+       TLS             TLS
 }
 
 type Services struct {
@@ -89,7 +86,7 @@ type Services struct {
 }
 
 type Service struct {
-       InternalURLs map[URL]ServiceInstance
+       InternalURLs map[URL]ServiceInstance `json:",omitempty"`
        ExternalURL  URL
 }
 
@@ -112,9 +109,10 @@ func (su URL) MarshalText() ([]byte, error) {
 
 type ServiceInstance struct{}
 
-type Logging struct {
-       Level  string
-       Format string
+type SystemLogs struct {
+       LogLevel                string
+       Format                  string
+       MaxRequestLogParamsSize int
 }
 
 type PostgreSQL struct {
@@ -148,59 +146,29 @@ type InstanceType struct {
        Preemptible     bool
 }
 
-type Dispatch struct {
-       // PEM encoded SSH key (RSA, DSA, or ECDSA) able to log in to
-       // cloud VMs.
-       PrivateKey string
-
-       // Max time for workers to come up before abandoning stale
-       // locks from previous run
-       StaleLockTimeout Duration
-
-       // Interval between queue polls
-       PollInterval Duration
-
-       // Interval between probes to each worker
-       ProbeInterval Duration
-
-       // Maximum total worker probes per second
-       MaxProbesPerSecond int
-
-       // Time before repeating SIGTERM when killing a container
-       TimeoutSignal Duration
-
-       // Time to give up on SIGTERM and write off the worker
-       TimeoutTERM Duration
+type ContainersConfig struct {
+       CloudVMs           CloudVMsConfig
+       DispatchPrivateKey string
+       StaleLockTimeout   Duration
 }
 
-type CloudVMs struct {
-       // Shell command that exits zero IFF the VM is fully booted
-       // and ready to run containers, e.g., "mount | grep
-       // /encrypted-tmp"
-       BootProbeCommand string
-
-       // Listening port (name or number) of SSH servers on worker
-       // VMs
-       SSHPort string
+type CloudVMsConfig struct {
+       Enable bool
 
-       SyncInterval Duration
-
-       // Maximum idle time before automatic shutdown
-       TimeoutIdle Duration
-
-       // Maximum booting time before automatic shutdown
-       TimeoutBooting Duration
-
-       // Maximum time with no successful probes before automatic shutdown
-       TimeoutProbe Duration
-
-       // Time after shutdown to retry shutdown
-       TimeoutShutdown Duration
-
-       // Maximum create/destroy-instance operations per second
+       BootProbeCommand     string
+       ImageID              string
        MaxCloudOpsPerSecond int
-
-       ImageID string
+       MaxProbesPerSecond   int
+       PollInterval         Duration
+       ProbeInterval        Duration
+       SSHPort              string
+       SyncInterval         Duration
+       TimeoutBooting       Duration
+       TimeoutIdle          Duration
+       TimeoutProbe         Duration
+       TimeoutShutdown      Duration
+       TimeoutSignal        Duration
+       TimeoutTERM          Duration
 
        Driver           string
        DriverParameters json.RawMessage
@@ -264,51 +232,16 @@ func (it *InstanceTypeMap) UnmarshalJSON(data []byte) error {
        return nil
 }
 
-// GetNodeProfile returns a NodeProfile for the given hostname. An
-// error is returned if the appropriate configuration can't be
-// determined (e.g., this does not appear to be a system node). If
-// node is empty, use the OS-reported hostname.
-func (cc *Cluster) GetNodeProfile(node string) (*NodeProfile, error) {
-       if node == "" {
-               hostname, err := os.Hostname()
-               if err != nil {
-                       return nil, err
-               }
-               node = hostname
-       }
-       if cfg, ok := cc.NodeProfiles[node]; ok {
-               return &cfg, nil
-       }
-       // If node is not listed, but "*" gives a default system node
-       // config, use the default config.
-       if cfg, ok := cc.NodeProfiles["*"]; ok {
-               return &cfg, nil
-       }
-       return nil, fmt.Errorf("config does not provision host %q as a system node", node)
-}
-
-type NodeProfile struct {
-       Controller    SystemServiceInstance `json:"arvados-controller"`
-       Health        SystemServiceInstance `json:"arvados-health"`
-       Keepbalance   SystemServiceInstance `json:"keep-balance"`
-       Keepproxy     SystemServiceInstance `json:"keepproxy"`
-       Keepstore     SystemServiceInstance `json:"keepstore"`
-       Keepweb       SystemServiceInstance `json:"keep-web"`
-       Nodemanager   SystemServiceInstance `json:"arvados-node-manager"`
-       DispatchCloud SystemServiceInstance `json:"arvados-dispatch-cloud"`
-       RailsAPI      SystemServiceInstance `json:"arvados-api-server"`
-       Websocket     SystemServiceInstance `json:"arvados-ws"`
-       Workbench     SystemServiceInstance `json:"arvados-workbench"`
-}
-
 type ServiceName string
 
 const (
        ServiceNameRailsAPI      ServiceName = "arvados-api-server"
        ServiceNameController    ServiceName = "arvados-controller"
        ServiceNameDispatchCloud ServiceName = "arvados-dispatch-cloud"
+       ServiceNameHealth        ServiceName = "arvados-health"
        ServiceNameNodemanager   ServiceName = "arvados-node-manager"
-       ServiceNameWorkbench     ServiceName = "arvados-workbench"
+       ServiceNameWorkbench1    ServiceName = "arvados-workbench1"
+       ServiceNameWorkbench2    ServiceName = "arvados-workbench2"
        ServiceNameWebsocket     ServiceName = "arvados-ws"
        ServiceNameKeepbalance   ServiceName = "keep-balance"
        ServiceNameKeepweb       ServiceName = "keep-web"
@@ -316,29 +249,25 @@ const (
        ServiceNameKeepstore     ServiceName = "keepstore"
 )
 
-// ServicePorts returns the configured listening address (or "" if
-// disabled) for each service on the node.
-func (np *NodeProfile) ServicePorts() map[ServiceName]string {
-       return map[ServiceName]string{
-               ServiceNameRailsAPI:      np.RailsAPI.Listen,
-               ServiceNameController:    np.Controller.Listen,
-               ServiceNameDispatchCloud: np.DispatchCloud.Listen,
-               ServiceNameNodemanager:   np.Nodemanager.Listen,
-               ServiceNameWorkbench:     np.Workbench.Listen,
-               ServiceNameWebsocket:     np.Websocket.Listen,
-               ServiceNameKeepbalance:   np.Keepbalance.Listen,
-               ServiceNameKeepweb:       np.Keepweb.Listen,
-               ServiceNameKeepproxy:     np.Keepproxy.Listen,
-               ServiceNameKeepstore:     np.Keepstore.Listen,
+// Map returns all services as a map, suitable for iterating over all
+// services or looking up a service by name.
+func (svcs Services) Map() map[ServiceName]Service {
+       return map[ServiceName]Service{
+               ServiceNameRailsAPI:      svcs.RailsAPI,
+               ServiceNameController:    svcs.Controller,
+               ServiceNameDispatchCloud: svcs.DispatchCloud,
+               ServiceNameHealth:        svcs.Health,
+               ServiceNameNodemanager:   svcs.Nodemanager,
+               ServiceNameWorkbench1:    svcs.Workbench1,
+               ServiceNameWorkbench2:    svcs.Workbench2,
+               ServiceNameWebsocket:     svcs.Websocket,
+               ServiceNameKeepbalance:   svcs.Keepbalance,
+               ServiceNameKeepweb:       svcs.WebDAV,
+               ServiceNameKeepproxy:     svcs.Keepproxy,
+               ServiceNameKeepstore:     svcs.Keepstore,
        }
 }
 
-type SystemServiceInstance struct {
-       Listen   string
-       TLS      bool
-       Insecure bool
-}
-
 type TLS struct {
        Certificate string
        Key         string
index 25eed010f26c534ef8e36dfa119065731d1e2ac4..d3e11c7a5e673aebe84cbb98a807fa0ef1806b64 100644 (file)
@@ -23,7 +23,7 @@ func (d *Duration) UnmarshalJSON(data []byte) error {
 }
 
 // MarshalJSON implements json.Marshaler.
-func (d *Duration) MarshalJSON() ([]byte, error) {
+func (d Duration) MarshalJSON() ([]byte, error) {
        return json.Marshal(d.String())
 }
 
index 2ae2bd8924e23b583a267091cc6b9985e52d3422..7fd03b120a7f34240393f884f88992b885499e1f 100644 (file)
@@ -23,7 +23,6 @@ import (
        "testing"
        "time"
 
-       "git.curoverse.com/arvados.git/sdk/go/arvadostest"
        check "gopkg.in/check.v1"
 )
 
@@ -87,7 +86,7 @@ type CollectionFSSuite struct {
 
 func (s *CollectionFSSuite) SetUpTest(c *check.C) {
        s.client = NewClientFromEnv()
-       err := s.client.RequestAndDecode(&s.coll, "GET", "arvados/v1/collections/"+arvadostest.FooAndBarFilesInDirUUID, nil, nil)
+       err := s.client.RequestAndDecode(&s.coll, "GET", "arvados/v1/collections/"+fixtureFooAndBarFilesInDirUUID, nil, nil)
        c.Assert(err, check.IsNil)
        s.kc = &keepClientStub{
                blocks: map[string][]byte{
index 1a06ce14632af5e1dc7f219208307291009d8da9..49e7d675f8b5c6729b61d94d0f271f615c68e924 100644 (file)
@@ -12,7 +12,6 @@ import (
        "path/filepath"
        "strings"
 
-       "git.curoverse.com/arvados.git/sdk/go/arvadostest"
        check "gopkg.in/check.v1"
 )
 
@@ -121,7 +120,7 @@ func (s *SiteFSSuite) TestProjectReaddirAfterLoadOne(c *check.C) {
 func (s *SiteFSSuite) TestSlashInName(c *check.C) {
        badCollection := Collection{
                Name:      "bad/collection",
-               OwnerUUID: arvadostest.AProjectUUID,
+               OwnerUUID: fixtureAProjectUUID,
        }
        err := s.client.RequestAndDecode(&badCollection, "POST", "arvados/v1/collections", s.client.UpdateBody(&badCollection), nil)
        c.Assert(err, check.IsNil)
@@ -130,7 +129,7 @@ func (s *SiteFSSuite) TestSlashInName(c *check.C) {
        badProject := Group{
                Name:       "bad/project",
                GroupClass: "project",
-               OwnerUUID:  arvadostest.AProjectUUID,
+               OwnerUUID:  fixtureAProjectUUID,
        }
        err = s.client.RequestAndDecode(&badProject, "POST", "arvados/v1/groups", s.client.UpdateBody(&badProject), nil)
        c.Assert(err, check.IsNil)
@@ -157,7 +156,7 @@ func (s *SiteFSSuite) TestProjectUpdatedByOther(c *check.C) {
 
        oob := Collection{
                Name:      "oob",
-               OwnerUUID: arvadostest.AProjectUUID,
+               OwnerUUID: fixtureAProjectUUID,
        }
        err = s.client.RequestAndDecode(&oob, "POST", "arvados/v1/collections", s.client.UpdateBody(&oob), nil)
        c.Assert(err, check.IsNil)
index 80028dc5985bd46e510c45f102eaf7d2ac518287..fff0b7e010b22b1811991ce3b6249093c50b616b 100644 (file)
@@ -8,10 +8,22 @@ import (
        "net/http"
        "os"
 
-       "git.curoverse.com/arvados.git/sdk/go/arvadostest"
        check "gopkg.in/check.v1"
 )
 
+const (
+       // Importing arvadostest would be an import cycle, so these
+       // fixtures are duplicated here [until fs moves to a separate
+       // package].
+       fixtureActiveToken             = "3kg6k6lzmp9kj5cpkcoxie963cmvjahbt2fod9zru30k1jqdmi"
+       fixtureAProjectUUID            = "zzzzz-j7d0g-v955i6s2oi1cbso"
+       fixtureFooAndBarFilesInDirUUID = "zzzzz-4zz18-foonbarfilesdir"
+       fixtureFooCollectionName       = "zzzzz-4zz18-fy296fx3hot09f7 added sometime"
+       fixtureFooCollectionPDH        = "1f4b0bc7583c2a7f9102c395f4ffc5e3+45"
+       fixtureFooCollection           = "zzzzz-4zz18-fy296fx3hot09f7"
+       fixtureNonexistentCollection   = "zzzzz-4zz18-totallynotexist"
+)
+
 var _ = check.Suite(&SiteFSSuite{})
 
 type SiteFSSuite struct {
@@ -23,7 +35,7 @@ type SiteFSSuite struct {
 func (s *SiteFSSuite) SetUpTest(c *check.C) {
        s.client = &Client{
                APIHost:   os.Getenv("ARVADOS_API_HOST"),
-               AuthToken: arvadostest.ActiveToken,
+               AuthToken: fixtureActiveToken,
                Insecure:  true,
        }
        s.kc = &keepClientStub{
@@ -53,16 +65,16 @@ func (s *SiteFSSuite) TestByUUIDAndPDH(c *check.C) {
        c.Check(err, check.IsNil)
        c.Check(len(fis), check.Equals, 0)
 
-       err = s.fs.Mkdir("/by_id/"+arvadostest.FooCollection, 0755)
+       err = s.fs.Mkdir("/by_id/"+fixtureFooCollection, 0755)
        c.Check(err, check.Equals, os.ErrExist)
 
-       f, err = s.fs.Open("/by_id/" + arvadostest.NonexistentCollection)
+       f, err = s.fs.Open("/by_id/" + fixtureNonexistentCollection)
        c.Assert(err, check.Equals, os.ErrNotExist)
 
        for _, path := range []string{
-               arvadostest.FooCollection,
-               arvadostest.FooPdh,
-               arvadostest.AProjectUUID + "/" + arvadostest.FooCollectionName,
+               fixtureFooCollection,
+               fixtureFooCollectionPDH,
+               fixtureAProjectUUID + "/" + fixtureFooCollectionName,
        } {
                f, err = s.fs.Open("/by_id/" + path)
                c.Assert(err, check.IsNil)
@@ -74,7 +86,7 @@ func (s *SiteFSSuite) TestByUUIDAndPDH(c *check.C) {
                c.Check(names, check.DeepEquals, []string{"foo"})
        }
 
-       f, err = s.fs.Open("/by_id/" + arvadostest.AProjectUUID + "/A Subproject/baz_file")
+       f, err = s.fs.Open("/by_id/" + fixtureAProjectUUID + "/A Subproject/baz_file")
        c.Assert(err, check.IsNil)
        fis, err = f.Readdir(-1)
        var names []string
@@ -83,15 +95,15 @@ func (s *SiteFSSuite) TestByUUIDAndPDH(c *check.C) {
        }
        c.Check(names, check.DeepEquals, []string{"baz"})
 
-       _, err = s.fs.OpenFile("/by_id/"+arvadostest.NonexistentCollection, os.O_RDWR|os.O_CREATE, 0755)
+       _, err = s.fs.OpenFile("/by_id/"+fixtureNonexistentCollection, os.O_RDWR|os.O_CREATE, 0755)
        c.Check(err, check.Equals, ErrInvalidOperation)
-       err = s.fs.Rename("/by_id/"+arvadostest.FooCollection, "/by_id/beep")
+       err = s.fs.Rename("/by_id/"+fixtureFooCollection, "/by_id/beep")
        c.Check(err, check.Equals, ErrInvalidArgument)
-       err = s.fs.Rename("/by_id/"+arvadostest.FooCollection+"/foo", "/by_id/beep")
+       err = s.fs.Rename("/by_id/"+fixtureFooCollection+"/foo", "/by_id/beep")
        c.Check(err, check.Equals, ErrInvalidArgument)
        _, err = s.fs.Stat("/by_id/beep")
        c.Check(err, check.Equals, os.ErrNotExist)
-       err = s.fs.Rename("/by_id/"+arvadostest.FooCollection+"/foo", "/by_id/"+arvadostest.FooCollection+"/bar")
+       err = s.fs.Rename("/by_id/"+fixtureFooCollection+"/foo", "/by_id/"+fixtureFooCollection+"/bar")
        c.Check(err, check.IsNil)
 
        err = s.fs.Rename("/by_id", "/beep")
index 4f648e9b437e7b5eead7abf4b0db302011725cb7..95b83265a05a4835363975cf8720157c0e9171da 100644 (file)
@@ -26,7 +26,6 @@ const (
        FooBarDirCollection     = "zzzzz-4zz18-foonbarfilesdir"
        WazVersion1Collection   = "zzzzz-4zz18-25k12570yk1ver1"
        UserAgreementPDH        = "b519d9cb706a29fc7ea24dbea2f05851+93"
-       FooPdh                  = "1f4b0bc7583c2a7f9102c395f4ffc5e3+45"
        HelloWorldPdh           = "55713e6a34081eb03609e7ad5fcad129+62"
 
        AProjectUUID    = "zzzzz-j7d0g-v955i6s2oi1cbso"
index 89925a957d381b322f51c528cebea7a2d45fb222..80735f86eb613d86d3b57f0a41af3813de6d6aa1 100644 (file)
@@ -6,6 +6,9 @@ package arvadostest
 
 import (
        "net/http"
+       "net/url"
+
+       "git.curoverse.com/arvados.git/sdk/go/arvados"
 )
 
 // StubResponse struct with response status and body
@@ -37,3 +40,22 @@ func (stub *ServerStub) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
                resp.Write([]byte(``))
        }
 }
+
+// SetServiceURL overrides the given service config/discovery with the
+// given internalURLs.
+//
+// ExternalURL is set to the last internalURL, which only aims to
+// address the case where there is only one.
+//
+// SetServiceURL panics on errors.
+func SetServiceURL(service *arvados.Service, internalURLs ...string) {
+       service.InternalURLs = map[arvados.URL]arvados.ServiceInstance{}
+       for _, u := range internalURLs {
+               u, err := url.Parse(u)
+               if err != nil {
+                       panic(err)
+               }
+               service.InternalURLs[arvados.URL(*u)] = arvados.ServiceInstance{}
+               service.ExternalURL = arvados.URL(*u)
+       }
+}
index 564331327a8d53ad250b044112f25e1b07730444..acfdbb7f8fc713517628314367a65234dc7fbd3d 100644 (file)
@@ -9,8 +9,8 @@ import (
        "encoding/json"
        "errors"
        "fmt"
-       "net"
        "net/http"
+       "net/url"
        "sync"
        "time"
 
@@ -28,7 +28,7 @@ type Aggregator struct {
        httpClient *http.Client
        timeout    arvados.Duration
 
-       Config *arvados.Config
+       Cluster *arvados.Cluster
 
        // If non-nil, Log is called after handling each request.
        Log func(*http.Request, error)
@@ -42,6 +42,10 @@ func (agg *Aggregator) setup() {
        }
 }
 
+func (agg *Aggregator) CheckHealth() error {
+       return nil
+}
+
 func (agg *Aggregator) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
        agg.setupOnce.Do(agg.setup)
        sendErr := func(statusCode int, err error) {
@@ -54,13 +58,7 @@ func (agg *Aggregator) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
 
        resp.Header().Set("Content-Type", "application/json")
 
-       cluster, err := agg.Config.GetCluster("")
-       if err != nil {
-               err = fmt.Errorf("arvados.GetCluster(): %s", err)
-               sendErr(http.StatusInternalServerError, err)
-               return
-       }
-       if !agg.checkAuth(req, cluster) {
+       if !agg.checkAuth(req) {
                sendErr(http.StatusUnauthorized, errUnauthorized)
                return
        }
@@ -68,7 +66,7 @@ func (agg *Aggregator) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
                sendErr(http.StatusNotFound, errNotFound)
                return
        }
-       json.NewEncoder(resp).Encode(agg.ClusterHealth(cluster))
+       json.NewEncoder(resp).Encode(agg.ClusterHealth())
        if agg.Log != nil {
                agg.Log(req, nil)
        }
@@ -104,7 +102,7 @@ type ServiceHealth struct {
        N      int    `json:"n"`
 }
 
-func (agg *Aggregator) ClusterHealth(cluster *arvados.Cluster) ClusterHealthResponse {
+func (agg *Aggregator) ClusterHealth() ClusterHealthResponse {
        resp := ClusterHealthResponse{
                Health:   "OK",
                Checks:   make(map[string]CheckResult),
@@ -113,46 +111,41 @@ func (agg *Aggregator) ClusterHealth(cluster *arvados.Cluster) ClusterHealthResp
 
        mtx := sync.Mutex{}
        wg := sync.WaitGroup{}
-       for profileName, profile := range cluster.NodeProfiles {
-               for svc, addr := range profile.ServicePorts() {
-                       // Ensure svc is listed in resp.Services.
-                       mtx.Lock()
-                       if _, ok := resp.Services[svc]; !ok {
-                               resp.Services[svc] = ServiceHealth{Health: "ERROR"}
-                       }
-                       mtx.Unlock()
-
-                       if addr == "" {
-                               // svc is not expected on this node.
-                               continue
-                       }
+       for svcName, svc := range agg.Cluster.Services.Map() {
+               // Ensure svc is listed in resp.Services.
+               mtx.Lock()
+               if _, ok := resp.Services[svcName]; !ok {
+                       resp.Services[svcName] = ServiceHealth{Health: "ERROR"}
+               }
+               mtx.Unlock()
 
+               for addr := range svc.InternalURLs {
                        wg.Add(1)
-                       go func(profileName string, svc arvados.ServiceName, addr string) {
+                       go func(svcName arvados.ServiceName, addr arvados.URL) {
                                defer wg.Done()
                                var result CheckResult
-                               url, err := agg.pingURL(profileName, addr)
+                               pingURL, err := agg.pingURL(addr)
                                if err != nil {
                                        result = CheckResult{
                                                Health: "ERROR",
                                                Error:  err.Error(),
                                        }
                                } else {
-                                       result = agg.ping(url, cluster)
+                                       result = agg.ping(pingURL)
                                }
 
                                mtx.Lock()
                                defer mtx.Unlock()
-                               resp.Checks[fmt.Sprintf("%s+%s", svc, url)] = result
+                               resp.Checks[fmt.Sprintf("%s+%s", svcName, pingURL)] = result
                                if result.Health == "OK" {
-                                       h := resp.Services[svc]
+                                       h := resp.Services[svcName]
                                        h.N++
                                        h.Health = "OK"
-                                       resp.Services[svc] = h
+                                       resp.Services[svcName] = h
                                } else {
                                        resp.Health = "ERROR"
                                }
-                       }(profileName, svc, addr)
+                       }(svcName, addr)
                }
        }
        wg.Wait()
@@ -168,12 +161,12 @@ func (agg *Aggregator) ClusterHealth(cluster *arvados.Cluster) ClusterHealthResp
        return resp
 }
 
-func (agg *Aggregator) pingURL(node, addr string) (string, error) {
-       _, port, err := net.SplitHostPort(addr)
-       return "http://" + node + ":" + port + "/_health/ping", err
+func (agg *Aggregator) pingURL(svcURL arvados.URL) (*url.URL, error) {
+       base := url.URL(svcURL)
+       return base.Parse("/_health/ping")
 }
 
-func (agg *Aggregator) ping(url string, cluster *arvados.Cluster) (result CheckResult) {
+func (agg *Aggregator) ping(target *url.URL) (result CheckResult) {
        t0 := time.Now()
 
        var err error
@@ -186,11 +179,11 @@ func (agg *Aggregator) ping(url string, cluster *arvados.Cluster) (result CheckR
                }
        }()
 
-       req, err := http.NewRequest("GET", url, nil)
+       req, err := http.NewRequest("GET", target.String(), nil)
        if err != nil {
                return
        }
-       req.Header.Set("Authorization", "Bearer "+cluster.ManagementToken)
+       req.Header.Set("Authorization", "Bearer "+agg.Cluster.ManagementToken)
 
        ctx, cancel := context.WithTimeout(req.Context(), time.Duration(agg.timeout))
        defer cancel()
@@ -216,10 +209,10 @@ func (agg *Aggregator) ping(url string, cluster *arvados.Cluster) (result CheckR
        return
 }
 
-func (agg *Aggregator) checkAuth(req *http.Request, cluster *arvados.Cluster) bool {
+func (agg *Aggregator) checkAuth(req *http.Request) bool {
        creds := auth.CredentialsFromRequest(req)
        for _, token := range creds.Tokens {
-               if token != "" && token == cluster.ManagementToken {
+               if token != "" && token == agg.Cluster.ManagementToken {
                        return true
                }
        }
index 122355be987755b161d38a2e46e0bc2cc4f52208..3ede3b983a5a97e69164f5704e5d91c4cf49b02f 100644 (file)
@@ -30,13 +30,8 @@ func (s *AggregatorSuite) TestInterface(c *check.C) {
 }
 
 func (s *AggregatorSuite) SetUpTest(c *check.C) {
-       s.handler = &Aggregator{Config: &arvados.Config{
-               Clusters: map[string]arvados.Cluster{
-                       "zzzzz": {
-                               ManagementToken: arvadostest.ManagementToken,
-                               NodeProfiles:    map[string]arvados.NodeProfile{},
-                       },
-               },
+       s.handler = &Aggregator{Cluster: &arvados.Cluster{
+               ManagementToken: arvadostest.ManagementToken,
        }}
        s.req = httptest.NewRequest("GET", "/_health/all", nil)
        s.req.Header.Set("Authorization", "Bearer "+arvadostest.ManagementToken)
@@ -57,9 +52,9 @@ func (s *AggregatorSuite) TestBadAuth(c *check.C) {
        c.Check(s.resp.Code, check.Equals, http.StatusUnauthorized)
 }
 
-func (s *AggregatorSuite) TestEmptyConfig(c *check.C) {
+func (s *AggregatorSuite) TestNoServicesConfigured(c *check.C) {
        s.handler.ServeHTTP(s.resp, s.req)
-       s.checkOK(c)
+       s.checkUnhealthy(c)
 }
 
 func (s *AggregatorSuite) stubServer(handler http.Handler) (*httptest.Server, string) {
@@ -73,51 +68,18 @@ func (s *AggregatorSuite) stubServer(handler http.Handler) (*httptest.Server, st
        return srv, ":" + port
 }
 
-type unhealthyHandler struct{}
-
-func (*unhealthyHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
-       if req.URL.Path == "/_health/ping" {
-               resp.Write([]byte(`{"health":"ERROR","error":"the bends"}`))
-       } else {
-               http.Error(resp, "not found", http.StatusNotFound)
-       }
-}
-
 func (s *AggregatorSuite) TestUnhealthy(c *check.C) {
        srv, listen := s.stubServer(&unhealthyHandler{})
        defer srv.Close()
-       s.handler.Config.Clusters["zzzzz"].NodeProfiles["localhost"] = arvados.NodeProfile{
-               Keepstore: arvados.SystemServiceInstance{Listen: listen},
-       }
+       arvadostest.SetServiceURL(&s.handler.Cluster.Services.Keepstore, "http://localhost"+listen+"/")
        s.handler.ServeHTTP(s.resp, s.req)
        s.checkUnhealthy(c)
 }
 
-type healthyHandler struct{}
-
-func (*healthyHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
-       if req.URL.Path == "/_health/ping" {
-               resp.Write([]byte(`{"health":"OK"}`))
-       } else {
-               http.Error(resp, "not found", http.StatusNotFound)
-       }
-}
-
 func (s *AggregatorSuite) TestHealthy(c *check.C) {
        srv, listen := s.stubServer(&healthyHandler{})
        defer srv.Close()
-       s.handler.Config.Clusters["zzzzz"].NodeProfiles["localhost"] = arvados.NodeProfile{
-               Controller:    arvados.SystemServiceInstance{Listen: listen},
-               DispatchCloud: arvados.SystemServiceInstance{Listen: listen},
-               Keepbalance:   arvados.SystemServiceInstance{Listen: listen},
-               Keepproxy:     arvados.SystemServiceInstance{Listen: listen},
-               Keepstore:     arvados.SystemServiceInstance{Listen: listen},
-               Keepweb:       arvados.SystemServiceInstance{Listen: listen},
-               Nodemanager:   arvados.SystemServiceInstance{Listen: listen},
-               RailsAPI:      arvados.SystemServiceInstance{Listen: listen},
-               Websocket:     arvados.SystemServiceInstance{Listen: listen},
-               Workbench:     arvados.SystemServiceInstance{Listen: listen},
-       }
+       s.setAllServiceURLs(listen)
        s.handler.ServeHTTP(s.resp, s.req)
        resp := s.checkOK(c)
        svc := "keepstore+http://localhost" + listen + "/_health/ping"
@@ -132,21 +94,8 @@ func (s *AggregatorSuite) TestHealthyAndUnhealthy(c *check.C) {
        defer srvH.Close()
        srvU, listenU := s.stubServer(&unhealthyHandler{})
        defer srvU.Close()
-       s.handler.Config.Clusters["zzzzz"].NodeProfiles["localhost"] = arvados.NodeProfile{
-               Controller:    arvados.SystemServiceInstance{Listen: listenH},
-               DispatchCloud: arvados.SystemServiceInstance{Listen: listenH},
-               Keepbalance:   arvados.SystemServiceInstance{Listen: listenH},
-               Keepproxy:     arvados.SystemServiceInstance{Listen: listenH},
-               Keepstore:     arvados.SystemServiceInstance{Listen: listenH},
-               Keepweb:       arvados.SystemServiceInstance{Listen: listenH},
-               Nodemanager:   arvados.SystemServiceInstance{Listen: listenH},
-               RailsAPI:      arvados.SystemServiceInstance{Listen: listenH},
-               Websocket:     arvados.SystemServiceInstance{Listen: listenH},
-               Workbench:     arvados.SystemServiceInstance{Listen: listenH},
-       }
-       s.handler.Config.Clusters["zzzzz"].NodeProfiles["127.0.0.1"] = arvados.NodeProfile{
-               Keepstore: arvados.SystemServiceInstance{Listen: listenU},
-       }
+       s.setAllServiceURLs(listenH)
+       arvadostest.SetServiceURL(&s.handler.Cluster.Services.Keepstore, "http://localhost"+listenH+"/", "http://127.0.0.1"+listenU+"/")
        s.handler.ServeHTTP(s.resp, s.req)
        resp := s.checkUnhealthy(c)
        ep := resp.Checks["keepstore+http://localhost"+listenH+"/_health/ping"]
@@ -158,10 +107,25 @@ func (s *AggregatorSuite) TestHealthyAndUnhealthy(c *check.C) {
        c.Logf("%#v", ep)
 }
 
+func (s *AggregatorSuite) TestPingTimeout(c *check.C) {
+       s.handler.timeout = arvados.Duration(100 * time.Millisecond)
+       srv, listen := s.stubServer(&slowHandler{})
+       defer srv.Close()
+       arvadostest.SetServiceURL(&s.handler.Cluster.Services.Keepstore, "http://localhost"+listen+"/")
+       s.handler.ServeHTTP(s.resp, s.req)
+       resp := s.checkUnhealthy(c)
+       ep := resp.Checks["keepstore+http://localhost"+listen+"/_health/ping"]
+       c.Check(ep.Health, check.Equals, "ERROR")
+       c.Check(ep.HTTPStatusCode, check.Equals, 0)
+       rt, err := ep.ResponseTime.Float64()
+       c.Check(err, check.IsNil)
+       c.Check(rt > 0.005, check.Equals, true)
+}
+
 func (s *AggregatorSuite) checkError(c *check.C) {
        c.Check(s.resp.Code, check.Not(check.Equals), http.StatusOK)
        var resp ClusterHealthResponse
-       err := json.NewDecoder(s.resp.Body).Decode(&resp)
+       err := json.Unmarshal(s.resp.Body.Bytes(), &resp)
        c.Check(err, check.IsNil)
        c.Check(resp.Health, check.Not(check.Equals), "OK")
 }
@@ -177,36 +141,60 @@ func (s *AggregatorSuite) checkOK(c *check.C) ClusterHealthResponse {
 func (s *AggregatorSuite) checkResult(c *check.C, health string) ClusterHealthResponse {
        c.Check(s.resp.Code, check.Equals, http.StatusOK)
        var resp ClusterHealthResponse
-       err := json.NewDecoder(s.resp.Body).Decode(&resp)
+       c.Log(s.resp.Body.String())
+       err := json.Unmarshal(s.resp.Body.Bytes(), &resp)
        c.Check(err, check.IsNil)
        c.Check(resp.Health, check.Equals, health)
        return resp
 }
 
-type slowHandler struct{}
+func (s *AggregatorSuite) setAllServiceURLs(listen string) {
+       svcs := &s.handler.Cluster.Services
+       for _, svc := range []*arvados.Service{
+               &svcs.Controller,
+               &svcs.DispatchCloud,
+               &svcs.Keepbalance,
+               &svcs.Keepproxy,
+               &svcs.Keepstore,
+               &svcs.Health,
+               &svcs.Nodemanager,
+               &svcs.RailsAPI,
+               &svcs.WebDAV,
+               &svcs.Websocket,
+               &svcs.Workbench1,
+               &svcs.Workbench2,
+       } {
+               arvadostest.SetServiceURL(svc, "http://localhost"+listen+"/")
+       }
+}
 
-func (*slowHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
+type unhealthyHandler struct{}
+
+func (*unhealthyHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
+       if req.URL.Path == "/_health/ping" {
+               resp.Write([]byte(`{"health":"ERROR","error":"the bends"}`))
+       } else {
+               http.Error(resp, "not found", http.StatusNotFound)
+       }
+}
+
+type healthyHandler struct{}
+
+func (*healthyHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
        if req.URL.Path == "/_health/ping" {
-               time.Sleep(3 * time.Second)
                resp.Write([]byte(`{"health":"OK"}`))
        } else {
                http.Error(resp, "not found", http.StatusNotFound)
        }
 }
 
-func (s *AggregatorSuite) TestPingTimeout(c *check.C) {
-       s.handler.timeout = arvados.Duration(100 * time.Millisecond)
-       srv, listen := s.stubServer(&slowHandler{})
-       defer srv.Close()
-       s.handler.Config.Clusters["zzzzz"].NodeProfiles["localhost"] = arvados.NodeProfile{
-               Keepstore: arvados.SystemServiceInstance{Listen: listen},
+type slowHandler struct{}
+
+func (*slowHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
+       if req.URL.Path == "/_health/ping" {
+               time.Sleep(3 * time.Second)
+               resp.Write([]byte(`{"health":"OK"}`))
+       } else {
+               http.Error(resp, "not found", http.StatusNotFound)
        }
-       s.handler.ServeHTTP(s.resp, s.req)
-       resp := s.checkUnhealthy(c)
-       ep := resp.Checks["keepstore+http://localhost"+listen+"/_health/ping"]
-       c.Check(ep.Health, check.Equals, "ERROR")
-       c.Check(ep.HTTPStatusCode, check.Equals, 0)
-       rt, err := ep.ResponseTime.Float64()
-       c.Check(err, check.IsNil)
-       c.Check(rt > 0.005, check.Equals, true)
 }
index a36bbfc6fd52eb84446c611ce7c549af300c9828..fea0578abdeca4d70a87cfadf0b9fe95825eb866 100644 (file)
@@ -414,29 +414,31 @@ def run_controller():
 Clusters:
   zzzzz:
     ManagementToken: e687950a23c3a9bceec28c6223a06c79
-    HTTPRequestTimeout: 30s
+    API:
+      RequestTimeout: 30s
     PostgreSQL:
       ConnectionPool: 32
       Connection:
-        host: {}
-        dbname: {}
-        user: {}
-        password: {}
-    NodeProfiles:
-      "*":
-        "arvados-controller":
-          Listen: ":{}"
-        "arvados-api-server":
-          Listen: ":{}"
-          TLS: true
-          Insecure: true
+        host: {dbhost}
+        dbname: {dbname}
+        user: {dbuser}
+        password: {dbpass}
+    TLS:
+      Insecure: true
+    Services:
+      Controller:
+        InternalURLs:
+          "http://localhost:{controllerport}": {{}}
+      RailsAPI:
+        InternalURLs:
+          "https://localhost:{railsport}": {{}}
         """.format(
-            _dbconfig('host'),
-            _dbconfig('database'),
-            _dbconfig('username'),
-            _dbconfig('password'),
-            port,
-            rails_api_port,
+            dbhost=_dbconfig('host'),
+            dbname=_dbconfig('database'),
+            dbuser=_dbconfig('username'),
+            dbpass=_dbconfig('password'),
+            controllerport=port,
+            railsport=rails_api_port,
         ))
     logf = open(_logfilename('controller'), 'a')
     controller = subprocess.Popen(
index 9ea36315f4950c186fd91d3a693f0e84c635c004..22a8fed58e2fc584333f5f3684e822a7a20ac21c 100644 (file)
@@ -48,6 +48,13 @@ if $arvados_config_defaults.empty?
   raise "Missing #{::Rails.root.to_s}/config/config.default.yml"
 end
 
+def remove_sample_entries(h)
+  return unless h.is_a? Hash
+  h.delete("SAMPLE")
+  h.each { |k, v| remove_sample_entries(v) }
+end
+remove_sample_entries($arvados_config_defaults)
+
 clusterID, clusterConfig = $arvados_config_defaults["Clusters"].first
 $arvados_config_defaults = clusterConfig
 $arvados_config_defaults["ClusterID"] = clusterID
index a2b560889f5b40cf488739a05bb77c63644152d8..c0fe38008d9a419f1ccfd17e10bde60803cfa29f 100644 (file)
@@ -111,7 +111,7 @@ func (s *copierSuite) TestSymlinkToMountedCollection(c *check.C) {
        // simulate mounted read-only collection
        s.cp.mounts["/mnt"] = arvados.Mount{
                Kind:             "collection",
-               PortableDataHash: arvadostest.FooPdh,
+               PortableDataHash: arvadostest.FooCollectionPDH,
        }
 
        // simulate mounted writable collection
@@ -125,7 +125,7 @@ func (s *copierSuite) TestSymlinkToMountedCollection(c *check.C) {
        c.Assert(f.Close(), check.IsNil)
        s.cp.mounts["/mnt-w"] = arvados.Mount{
                Kind:             "collection",
-               PortableDataHash: arvadostest.FooPdh,
+               PortableDataHash: arvadostest.FooCollectionPDH,
                Writable:         true,
        }
        s.cp.binds = append(s.cp.binds, bindtmp+":/mnt-w")
@@ -197,7 +197,7 @@ func (s *copierSuite) TestUnsupportedMountKindBelow(c *check.C) {
 func (s *copierSuite) TestWritableMountBelow(c *check.C) {
        s.cp.mounts["/ctr/outdir/mount"] = arvados.Mount{
                Kind:             "collection",
-               PortableDataHash: arvadostest.FooPdh,
+               PortableDataHash: arvadostest.FooCollectionPDH,
                Writable:         true,
        }
        c.Assert(os.MkdirAll(s.cp.hostOutputDir+"/mount", 0755), check.IsNil)
index 21fcf4d674e1015184f2c87413cb0fbb32a9f0d1..2f66b2461ebb81c7ee94399cc4dc426ac2315c33 100644 (file)
@@ -5,67 +5,24 @@
 package main
 
 import (
-       "flag"
-       "fmt"
-       "net/http"
+       "context"
+       "os"
 
+       "git.curoverse.com/arvados.git/lib/cmd"
+       "git.curoverse.com/arvados.git/lib/service"
        "git.curoverse.com/arvados.git/sdk/go/arvados"
        "git.curoverse.com/arvados.git/sdk/go/health"
-       "git.curoverse.com/arvados.git/sdk/go/httpserver"
-       log "github.com/sirupsen/logrus"
 )
 
-var version = "dev"
-
-func main() {
-       configFile := flag.String("config", arvados.DefaultConfigFile, "`path` to arvados configuration file")
-       getVersion := flag.Bool("version", false, "Print version information and exit.")
-       flag.Parse()
-
-       // Print version information if requested
-       if *getVersion {
-               fmt.Printf("arvados-health %s\n", version)
-               return
-       }
-
-       log.SetFormatter(&log.JSONFormatter{
-               TimestampFormat: "2006-01-02T15:04:05.000000000Z07:00",
-       })
-       log.Printf("arvados-health %s started", version)
+var (
+       version             = "dev"
+       command cmd.Handler = service.Command(arvados.ServiceNameController, newHandler)
+)
 
-       cfg, err := arvados.GetConfig(*configFile)
-       if err != nil {
-               log.Fatal(err)
-       }
-       clusterCfg, err := cfg.GetCluster("")
-       if err != nil {
-               log.Fatal(err)
-       }
-       nodeCfg, err := clusterCfg.GetNodeProfile("")
-       if err != nil {
-               log.Fatal(err)
-       }
+func newHandler(ctx context.Context, cluster *arvados.Cluster, _ string) service.Handler {
+       return &health.Aggregator{Cluster: cluster}
+}
 
-       log := log.WithField("Service", "Health")
-       srv := &httpserver.Server{
-               Addr: nodeCfg.Health.Listen,
-               Server: http.Server{
-                       Handler: &health.Aggregator{
-                               Config: cfg,
-                               Log: func(req *http.Request, err error) {
-                                       log.WithField("RemoteAddr", req.RemoteAddr).
-                                               WithField("Path", req.URL.Path).
-                                               WithError(err).
-                                               Info("HTTP request")
-                               },
-                       },
-               },
-       }
-       if err := srv.Start(); err != nil {
-               log.Fatal(err)
-       }
-       log.WithField("Listen", srv.Addr).Info("listening")
-       if err := srv.Wait(); err != nil {
-               log.Fatal(err)
-       }
+func main() {
+       os.Exit(command.RunCommand(os.Args[0], os.Args[1:], os.Stdin, os.Stdout, os.Stderr))
 }
index d147573eec72d402faec43c21da86a010f13dc94..d6dd389278e7ae4f05faab2450680a2112fb1545 100644 (file)
@@ -45,7 +45,7 @@ func (s *UnitSuite) TestCache(c *check.C) {
                coll, err = cache.Get(arv, arvadostest.FooCollection, false)
                c.Check(err, check.Equals, nil)
                c.Assert(coll, check.NotNil)
-               c.Check(coll.PortableDataHash, check.Equals, arvadostest.FooPdh)
+               c.Check(coll.PortableDataHash, check.Equals, arvadostest.FooCollectionPDH)
                c.Check(coll.ManifestText[:2], check.Equals, ". ")
        }
        s.checkCacheMetrics(c, cache.registry,
@@ -62,10 +62,10 @@ func (s *UnitSuite) TestCache(c *check.C) {
        // lookup.
        arv.ApiToken = arvadostest.ActiveToken
 
-       coll2, err := cache.Get(arv, arvadostest.FooPdh, false)
+       coll2, err := cache.Get(arv, arvadostest.FooCollectionPDH, false)
        c.Check(err, check.Equals, nil)
        c.Assert(coll2, check.NotNil)
-       c.Check(coll2.PortableDataHash, check.Equals, arvadostest.FooPdh)
+       c.Check(coll2.PortableDataHash, check.Equals, arvadostest.FooCollectionPDH)
        c.Check(coll2.ManifestText[:2], check.Equals, ". ")
        c.Check(coll2.ManifestText, check.Not(check.Equals), coll.ManifestText)
 
@@ -76,10 +76,10 @@ func (s *UnitSuite) TestCache(c *check.C) {
                "pdh_hits 4",
                "api_calls 2")
 
-       coll2, err = cache.Get(arv, arvadostest.FooPdh, false)
+       coll2, err = cache.Get(arv, arvadostest.FooCollectionPDH, false)
        c.Check(err, check.Equals, nil)
        c.Assert(coll2, check.NotNil)
-       c.Check(coll2.PortableDataHash, check.Equals, arvadostest.FooPdh)
+       c.Check(coll2.PortableDataHash, check.Equals, arvadostest.FooCollectionPDH)
        c.Check(coll2.ManifestText[:2], check.Equals, ". ")
 
        s.checkCacheMetrics(c, cache.registry,
@@ -118,7 +118,7 @@ func (s *UnitSuite) TestCacheForceReloadByPDH(c *check.C) {
        cache.registry = prometheus.NewRegistry()
 
        for _, forceReload := range []bool{false, true, false, true} {
-               _, err := cache.Get(arv, arvadostest.FooPdh, forceReload)
+               _, err := cache.Get(arv, arvadostest.FooCollectionPDH, forceReload)
                c.Check(err, check.Equals, nil)
        }
 
index 44d0b0ffefa743dc931eb448bcadce510e5abf92..1c93a2b91c0981840c5ac2dde998a55adb1d9b51 100644 (file)
@@ -298,8 +298,8 @@ func (s *IntegrationSuite) TestCadaverByID(c *check.C) {
                c.Check(stdout, check.Matches, `(?ms).*collection is empty.*`)
        }
        for _, path := range []string{
-               "/by_id/" + arvadostest.FooPdh,
-               "/by_id/" + arvadostest.FooPdh + "/",
+               "/by_id/" + arvadostest.FooCollectionPDH,
+               "/by_id/" + arvadostest.FooCollectionPDH + "/",
                "/by_id/" + arvadostest.FooCollection,
                "/by_id/" + arvadostest.FooCollection + "/",
        } {
index 7a015c91f9d07b56926dd480e0b30f47149af1c8..040638623748f8aa57150b314886871703157287 100644 (file)
@@ -59,7 +59,7 @@ func (s *UnitSuite) TestCORSPreflight(c *check.C) {
 }
 
 func (s *UnitSuite) TestInvalidUUID(c *check.C) {
-       bogusID := strings.Replace(arvadostest.FooPdh, "+", "-", 1) + "-"
+       bogusID := strings.Replace(arvadostest.FooCollectionPDH, "+", "-", 1) + "-"
        token := arvadostest.ActiveToken
        for _, trial := range []string{
                "http://keep-web/c=" + bogusID + "/foo",
@@ -186,8 +186,8 @@ func (s *IntegrationSuite) doVhostRequests(c *check.C, authz authorizer) {
                arvadostest.FooCollection + ".example.com/foo",
                arvadostest.FooCollection + "--collections.example.com/foo",
                arvadostest.FooCollection + "--collections.example.com/_/foo",
-               arvadostest.FooPdh + ".example.com/foo",
-               strings.Replace(arvadostest.FooPdh, "+", "-", -1) + "--collections.example.com/foo",
+               arvadostest.FooCollectionPDH + ".example.com/foo",
+               strings.Replace(arvadostest.FooCollectionPDH, "+", "-", -1) + "--collections.example.com/foo",
                arvadostest.FooBarDirCollection + ".example.com/dir1/foo",
        } {
                c.Log("doRequests: ", hostPath)
index a9830bc1de4715d2cfdaa39049106bcf95cce779..ab50641be19c780a7d0b6145353b2611d0b02578 100644 (file)
@@ -164,16 +164,16 @@ func (s *IntegrationSuite) Test200(c *check.C) {
                        dataMD5: "acbd18db4cc2f85cedef654fccc4a4d8",
                },
                {
-                       host:    strings.Replace(arvadostest.FooPdh, "+", "-", 1) + ".collections.example.com",
+                       host:    strings.Replace(arvadostest.FooCollectionPDH, "+", "-", 1) + ".collections.example.com",
                        path:    "/t=" + arvadostest.ActiveToken + "/foo",
                        dataMD5: "acbd18db4cc2f85cedef654fccc4a4d8",
                },
                {
-                       path:    "/c=" + arvadostest.FooPdh + "/t=" + arvadostest.ActiveToken + "/foo",
+                       path:    "/c=" + arvadostest.FooCollectionPDH + "/t=" + arvadostest.ActiveToken + "/foo",
                        dataMD5: "acbd18db4cc2f85cedef654fccc4a4d8",
                },
                {
-                       path:    "/c=" + strings.Replace(arvadostest.FooPdh, "+", "-", 1) + "/t=" + arvadostest.ActiveToken + "/_/foo",
+                       path:    "/c=" + strings.Replace(arvadostest.FooCollectionPDH, "+", "-", 1) + "/t=" + arvadostest.ActiveToken + "/_/foo",
                        dataMD5: "acbd18db4cc2f85cedef654fccc4a4d8",
                },
                {