Merge branch '14853-chapmanb-subprocess-merge'
authorEric Biagiotti <ebiagiotti@veritasgenetcs.com>
Thu, 21 Feb 2019 15:04:17 +0000 (10:04 -0500)
committerEric Biagiotti <ebiagiotti@veritasgenetcs.com>
Thu, 21 Feb 2019 15:04:17 +0000 (10:04 -0500)
refs #14853

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

26 files changed:
apps/workbench/.gitignore
apps/workbench/package-build.version [deleted file]
apps/workbench/test/the.patch [deleted file]
build/run-build-packages-python-and-ruby.sh
build/run-library.sh
build/run-tests.sh
lib/cloud/azure/azure.go [moved from lib/cloud/azure.go with 73% similarity]
lib/cloud/azure/azure_test.go [moved from lib/cloud/azure_test.go with 68% similarity]
lib/cloud/gocheck_test.go [deleted file]
lib/cloud/interfaces.go
lib/controller/federation_test.go
lib/controller/handler.go
lib/dispatchcloud/container/queue.go
lib/dispatchcloud/driver.go
lib/dispatchcloud/test/stub_driver.go
sdk/go/arvados/config.go
sdk/go/arvados/duration.go
services/api/.gitignore
services/api/app/models/collection.rb
services/api/app/models/container.rb
services/api/config/application.default.yml
services/api/db/migrate/20190214214814_add_container_lock_count.rb [new file with mode: 0644]
services/api/db/structure.sql
services/api/test/functional/arvados/v1/collections_controller_test.rb
services/api/test/unit/container_test.rb
vendor/vendor.json

index 156fc86a5eadee7b9cef56c004db808c8a3d8d03..25c7c3ef24ea04acc225e1d1295d424d3959a0cc 100644 (file)
@@ -44,3 +44,6 @@
 # npm-rails
 /node_modules
 /npm-debug.log
+
+# Generated when building distribution packages
+/package-build.version
diff --git a/apps/workbench/package-build.version b/apps/workbench/package-build.version
deleted file mode 100644 (file)
index 41eb2c7..0000000
+++ /dev/null
@@ -1 +0,0 @@
-1.2.1.20181126194329
diff --git a/apps/workbench/test/the.patch b/apps/workbench/test/the.patch
deleted file mode 100644 (file)
index 5a55679..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-+    echo -n 'geckodriver: '
-+    which geckodriver || fatal "No geckodriver. Unable to find Mozilla geckodriver. Please download the server from https://github.com/mozilla/geckodriver/releases and place it somewhere on your PATH. More info at https://developer.mozilla.org/en-US/docs/Mozilla/QA/Marionette/WebDriver."
-
index 35f8104450339c80c4f9e2ff92f50cd200f2b1cc..4c5f39a373e66cdf160ac71aaa7edf7fc47cd2e3 100755 (executable)
@@ -118,9 +118,6 @@ if [[ "$DEBUG" != 0 ]]; then
     DASHQ_UNLESS_DEBUG=
 fi
 
-EASY_INSTALL2=$(find_easy_install -$PYTHON2_VERSION "")
-EASY_INSTALL3=$(find_easy_install -$PYTHON3_VERSION 3)
-
 RUN_BUILD_PACKAGES_PATH="`dirname \"$0\"`"
 RUN_BUILD_PACKAGES_PATH="`( cd \"$RUN_BUILD_PACKAGES_PATH\" && pwd )`"  # absolutized and normalized
 if [ -z "$RUN_BUILD_PACKAGES_PATH" ] ; then
index 8bf2166d5e2a3c29e46494cba956f1cec47eeeb4..40589fd565c258240fed5fe1057fad5ab38993b1 100755 (executable)
@@ -274,7 +274,7 @@ test_package_presence() {
         echo ${repo_pkg_list} |grep -q ${complete_pkgname}
         if [ $? -eq 0 ] ; then
           echo "Package $complete_pkgname exists, not rebuilding!"
-          curl -o ./${complete_pkgname} http://apt.arvados.org/pool/${D}/main/${repo_subdir}/${complete_pkgname}
+          curl -s -o ./${complete_pkgname} http://apt.arvados.org/pool/${D}/main/${repo_subdir}/${complete_pkgname}
           return 1
         elif test -f "$WORKSPACE/packages/$TARGET/processed/${complete_pkgname}" ; then
           echo "Package $complete_pkgname exists, not rebuilding!"
@@ -287,11 +287,11 @@ test_package_presence() {
     else
       centos_repo="http://rpm.arvados.org/CentOS/7/dev/x86_64/"
 
-      repo_pkg_list=$(curl -o - ${centos_repo})
+      repo_pkg_list=$(curl -s -o - ${centos_repo})
       echo ${repo_pkg_list} |grep -q ${complete_pkgname}
       if [ $? -eq 0 ]; then
         echo "Package $complete_pkgname exists, not rebuilding!"
-        curl -o ./${complete_pkgname} ${centos_repo}${complete_pkgname}
+        curl -s -o ./${complete_pkgname} ${centos_repo}${complete_pkgname}
         return 1
       elif test -f "$WORKSPACE/packages/$TARGET/processed/${complete_pkgname}" ; then
         echo "Package $complete_pkgname exists, not rebuilding!"
index 96e6aea72ca10615c56d9734090dc7b716e024ec..9919c3e1751766892b72bcc806170b61f43e5999 100755 (executable)
@@ -77,6 +77,7 @@ lib/cmd
 lib/controller
 lib/crunchstat
 lib/cloud
+lib/cloud/azure
 lib/dispatchcloud
 lib/dispatchcloud/container
 lib/dispatchcloud/scheduler
@@ -932,6 +933,7 @@ gostuff=(
     lib/controller
     lib/crunchstat
     lib/cloud
+    lib/cloud/azure
     lib/dispatchcloud
     lib/dispatchcloud/container
     lib/dispatchcloud/scheduler
similarity index 73%
rename from lib/cloud/azure.go
rename to lib/cloud/azure/azure.go
index a194b33180b231cfb74964b11253d5aa6f8d0667..d745e7e54d27473147e3f214a213a4514a596131 100644 (file)
@@ -2,11 +2,12 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-package cloud
+package azure
 
 import (
        "context"
        "encoding/base64"
+       "encoding/json"
        "fmt"
        "net/http"
        "regexp"
@@ -15,6 +16,7 @@ import (
        "sync"
        "time"
 
+       "git.curoverse.com/arvados.git/lib/cloud"
        "git.curoverse.com/arvados.git/sdk/go/arvados"
        "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-06-01/compute"
        "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-06-01/network"
@@ -25,129 +27,130 @@ import (
        "github.com/Azure/go-autorest/autorest/azure/auth"
        "github.com/Azure/go-autorest/autorest/to"
        "github.com/jmcvetta/randutil"
-       "github.com/mitchellh/mapstructure"
        "github.com/sirupsen/logrus"
        "golang.org/x/crypto/ssh"
 )
 
-type AzureInstanceSetConfig struct {
-       SubscriptionID               string  `mapstructure:"subscription_id"`
-       ClientID                     string  `mapstructure:"key"`
-       ClientSecret                 string  `mapstructure:"secret"`
-       TenantID                     string  `mapstructure:"tenant_id"`
-       CloudEnv                     string  `mapstructure:"cloud_environment"`
-       ResourceGroup                string  `mapstructure:"resource_group"`
-       Location                     string  `mapstructure:"region"`
-       Network                      string  `mapstructure:"network"`
-       Subnet                       string  `mapstructure:"subnet"`
-       StorageAccount               string  `mapstructure:"storage_account"`
-       BlobContainer                string  `mapstructure:"blob_container"`
-       Image                        string  `mapstructure:"image"`
-       DeleteDanglingResourcesAfter float64 `mapstructure:"delete_dangling_resources_after"`
-}
-
-type VirtualMachinesClientWrapper interface {
-       CreateOrUpdate(ctx context.Context,
+// Driver is the azure implementation of the cloud.Driver interface.
+var Driver = cloud.DriverFunc(newAzureInstanceSet)
+
+type azureInstanceSetConfig struct {
+       SubscriptionID               string
+       ClientID                     string
+       ClientSecret                 string
+       TenantID                     string
+       CloudEnvironment             string
+       ResourceGroup                string
+       Location                     string
+       Network                      string
+       Subnet                       string
+       StorageAccount               string
+       BlobContainer                string
+       DeleteDanglingResourcesAfter arvados.Duration
+}
+
+type virtualMachinesClientWrapper interface {
+       createOrUpdate(ctx context.Context,
                resourceGroupName string,
                VMName string,
                parameters compute.VirtualMachine) (result compute.VirtualMachine, err error)
-       Delete(ctx context.Context, resourceGroupName string, VMName string) (result *http.Response, err error)
-       ListComplete(ctx context.Context, resourceGroupName string) (result compute.VirtualMachineListResultIterator, err error)
+       delete(ctx context.Context, resourceGroupName string, VMName string) (result *http.Response, err error)
+       listComplete(ctx context.Context, resourceGroupName string) (result compute.VirtualMachineListResultIterator, err error)
 }
 
-type VirtualMachinesClientImpl struct {
+type virtualMachinesClientImpl struct {
        inner compute.VirtualMachinesClient
 }
 
-func (cl *VirtualMachinesClientImpl) CreateOrUpdate(ctx context.Context,
+func (cl *virtualMachinesClientImpl) createOrUpdate(ctx context.Context,
        resourceGroupName string,
        VMName string,
        parameters compute.VirtualMachine) (result compute.VirtualMachine, err error) {
 
        future, err := cl.inner.CreateOrUpdate(ctx, resourceGroupName, VMName, parameters)
        if err != nil {
-               return compute.VirtualMachine{}, WrapAzureError(err)
+               return compute.VirtualMachine{}, wrapAzureError(err)
        }
        future.WaitForCompletionRef(ctx, cl.inner.Client)
        r, err := future.Result(cl.inner)
-       return r, WrapAzureError(err)
+       return r, wrapAzureError(err)
 }
 
-func (cl *VirtualMachinesClientImpl) Delete(ctx context.Context, resourceGroupName string, VMName string) (result *http.Response, err error) {
+func (cl *virtualMachinesClientImpl) delete(ctx context.Context, resourceGroupName string, VMName string) (result *http.Response, err error) {
        future, err := cl.inner.Delete(ctx, resourceGroupName, VMName)
        if err != nil {
-               return nil, WrapAzureError(err)
+               return nil, wrapAzureError(err)
        }
        err = future.WaitForCompletionRef(ctx, cl.inner.Client)
-       return future.Response(), WrapAzureError(err)
+       return future.Response(), wrapAzureError(err)
 }
 
-func (cl *VirtualMachinesClientImpl) ListComplete(ctx context.Context, resourceGroupName string) (result compute.VirtualMachineListResultIterator, err error) {
+func (cl *virtualMachinesClientImpl) listComplete(ctx context.Context, resourceGroupName string) (result compute.VirtualMachineListResultIterator, err error) {
        r, err := cl.inner.ListComplete(ctx, resourceGroupName)
-       return r, WrapAzureError(err)
+       return r, wrapAzureError(err)
 }
 
-type InterfacesClientWrapper interface {
-       CreateOrUpdate(ctx context.Context,
+type interfacesClientWrapper interface {
+       createOrUpdate(ctx context.Context,
                resourceGroupName string,
                networkInterfaceName string,
                parameters network.Interface) (result network.Interface, err error)
-       Delete(ctx context.Context, resourceGroupName string, networkInterfaceName string) (result *http.Response, err error)
-       ListComplete(ctx context.Context, resourceGroupName string) (result network.InterfaceListResultIterator, err error)
+       delete(ctx context.Context, resourceGroupName string, networkInterfaceName string) (result *http.Response, err error)
+       listComplete(ctx context.Context, resourceGroupName string) (result network.InterfaceListResultIterator, err error)
 }
 
-type InterfacesClientImpl struct {
+type interfacesClientImpl struct {
        inner network.InterfacesClient
 }
 
-func (cl *InterfacesClientImpl) Delete(ctx context.Context, resourceGroupName string, VMName string) (result *http.Response, err error) {
+func (cl *interfacesClientImpl) delete(ctx context.Context, resourceGroupName string, VMName string) (result *http.Response, err error) {
        future, err := cl.inner.Delete(ctx, resourceGroupName, VMName)
        if err != nil {
-               return nil, WrapAzureError(err)
+               return nil, wrapAzureError(err)
        }
        err = future.WaitForCompletionRef(ctx, cl.inner.Client)
-       return future.Response(), WrapAzureError(err)
+       return future.Response(), wrapAzureError(err)
 }
 
-func (cl *InterfacesClientImpl) CreateOrUpdate(ctx context.Context,
+func (cl *interfacesClientImpl) createOrUpdate(ctx context.Context,
        resourceGroupName string,
        networkInterfaceName string,
        parameters network.Interface) (result network.Interface, err error) {
 
        future, err := cl.inner.CreateOrUpdate(ctx, resourceGroupName, networkInterfaceName, parameters)
        if err != nil {
-               return network.Interface{}, WrapAzureError(err)
+               return network.Interface{}, wrapAzureError(err)
        }
        future.WaitForCompletionRef(ctx, cl.inner.Client)
        r, err := future.Result(cl.inner)
-       return r, WrapAzureError(err)
+       return r, wrapAzureError(err)
 }
 
-func (cl *InterfacesClientImpl) ListComplete(ctx context.Context, resourceGroupName string) (result network.InterfaceListResultIterator, err error) {
+func (cl *interfacesClientImpl) listComplete(ctx context.Context, resourceGroupName string) (result network.InterfaceListResultIterator, err error) {
        r, err := cl.inner.ListComplete(ctx, resourceGroupName)
-       return r, WrapAzureError(err)
+       return r, wrapAzureError(err)
 }
 
 var quotaRe = regexp.MustCompile(`(?i:exceed|quota|limit)`)
 
-type AzureRateLimitError struct {
+type azureRateLimitError struct {
        azure.RequestError
-       earliestRetry time.Time
+       firstRetry time.Time
 }
 
-func (ar *AzureRateLimitError) EarliestRetry() time.Time {
-       return ar.earliestRetry
+func (ar *azureRateLimitError) EarliestRetry() time.Time {
+       return ar.firstRetry
 }
 
-type AzureQuotaError struct {
+type azureQuotaError struct {
        azure.RequestError
 }
 
-func (ar *AzureQuotaError) IsQuotaError() bool {
+func (ar *azureQuotaError) IsQuotaError() bool {
        return true
 }
 
-func WrapAzureError(err error) error {
+func wrapAzureError(err error) error {
        de, ok := err.(autorest.DetailedError)
        if !ok {
                return err
@@ -174,21 +177,21 @@ func WrapAzureError(err error) error {
                                earliestRetry = time.Now().Add(20 * time.Second)
                        }
                }
-               return &AzureRateLimitError{*rq, earliestRetry}
+               return &azureRateLimitError{*rq, earliestRetry}
        }
        if rq.ServiceError == nil {
                return err
        }
        if quotaRe.FindString(rq.ServiceError.Code) != "" || quotaRe.FindString(rq.ServiceError.Message) != "" {
-               return &AzureQuotaError{*rq}
+               return &azureQuotaError{*rq}
        }
        return err
 }
 
-type AzureInstanceSet struct {
-       azconfig          AzureInstanceSetConfig
-       vmClient          VirtualMachinesClientWrapper
-       netClient         InterfacesClientWrapper
+type azureInstanceSet struct {
+       azconfig          azureInstanceSetConfig
+       vmClient          virtualMachinesClientWrapper
+       netClient         interfacesClientWrapper
        storageAcctClient storageacct.AccountsClient
        azureEnv          azure.Environment
        interfaces        map[string]network.Interface
@@ -202,12 +205,14 @@ type AzureInstanceSet struct {
        logger            logrus.FieldLogger
 }
 
-func NewAzureInstanceSet(config map[string]interface{}, dispatcherID InstanceSetID, logger logrus.FieldLogger) (prv InstanceSet, err error) {
-       azcfg := AzureInstanceSetConfig{}
-       if err = mapstructure.Decode(config, &azcfg); err != nil {
+func newAzureInstanceSet(config json.RawMessage, dispatcherID cloud.InstanceSetID, logger logrus.FieldLogger) (prv cloud.InstanceSet, err error) {
+       azcfg := azureInstanceSetConfig{}
+       err = json.Unmarshal(config, &azcfg)
+       if err != nil {
                return nil, err
        }
-       ap := AzureInstanceSet{logger: logger}
+
+       ap := azureInstanceSet{logger: logger}
        err = ap.setup(azcfg, string(dispatcherID))
        if err != nil {
                return nil, err
@@ -215,13 +220,13 @@ func NewAzureInstanceSet(config map[string]interface{}, dispatcherID InstanceSet
        return &ap, nil
 }
 
-func (az *AzureInstanceSet) setup(azcfg AzureInstanceSetConfig, dispatcherID string) (err error) {
+func (az *azureInstanceSet) setup(azcfg azureInstanceSetConfig, dispatcherID string) (err error) {
        az.azconfig = azcfg
        vmClient := compute.NewVirtualMachinesClient(az.azconfig.SubscriptionID)
        netClient := network.NewInterfacesClient(az.azconfig.SubscriptionID)
        storageAcctClient := storageacct.NewAccountsClient(az.azconfig.SubscriptionID)
 
-       az.azureEnv, err = azure.EnvironmentFromName(az.azconfig.CloudEnv)
+       az.azureEnv, err = azure.EnvironmentFromName(az.azconfig.CloudEnvironment)
        if err != nil {
                return err
        }
@@ -241,8 +246,8 @@ func (az *AzureInstanceSet) setup(azcfg AzureInstanceSetConfig, dispatcherID str
        netClient.Authorizer = authorizer
        storageAcctClient.Authorizer = authorizer
 
-       az.vmClient = &VirtualMachinesClientImpl{vmClient}
-       az.netClient = &InterfacesClientImpl{netClient}
+       az.vmClient = &virtualMachinesClientImpl{vmClient}
+       az.netClient = &interfacesClientImpl{netClient}
        az.storageAcctClient = storageAcctClient
 
        az.dispatcherID = dispatcherID
@@ -260,7 +265,7 @@ func (az *AzureInstanceSet) setup(azcfg AzureInstanceSetConfig, dispatcherID str
                                tk.Stop()
                                return
                        case <-tk.C:
-                               az.ManageBlobs()
+                               az.manageBlobs()
                        }
                }
        }()
@@ -268,14 +273,14 @@ func (az *AzureInstanceSet) setup(azcfg AzureInstanceSetConfig, dispatcherID str
        az.deleteNIC = make(chan string)
        az.deleteBlob = make(chan storage.Blob)
 
-       for i := 0; i < 4; i += 1 {
+       for i := 0; i < 4; i++ {
                go func() {
                        for {
                                nicname, ok := <-az.deleteNIC
                                if !ok {
                                        return
                                }
-                               _, delerr := az.netClient.Delete(context.Background(), az.azconfig.ResourceGroup, nicname)
+                               _, delerr := az.netClient.delete(context.Background(), az.azconfig.ResourceGroup, nicname)
                                if delerr != nil {
                                        az.logger.WithError(delerr).Warnf("Error deleting %v", nicname)
                                } else {
@@ -302,11 +307,11 @@ func (az *AzureInstanceSet) setup(azcfg AzureInstanceSetConfig, dispatcherID str
        return nil
 }
 
-func (az *AzureInstanceSet) Create(
+func (az *azureInstanceSet) Create(
        instanceType arvados.InstanceType,
-       imageIImageID,
-       newTags InstanceTags,
-       publicKey ssh.PublicKey) (Instance, error) {
+       imageID cloud.ImageID,
+       newTags cloud.InstanceTags,
+       publicKey ssh.PublicKey) (cloud.Instance, error) {
 
        az.stopWg.Add(1)
        defer az.stopWg.Done()
@@ -355,12 +360,12 @@ func (az *AzureInstanceSet) Create(
                        },
                },
        }
-       nic, err := az.netClient.CreateOrUpdate(az.ctx, az.azconfig.ResourceGroup, name+"-nic", nicParameters)
+       nic, err := az.netClient.createOrUpdate(az.ctx, az.azconfig.ResourceGroup, name+"-nic", nicParameters)
        if err != nil {
-               return nil, WrapAzureError(err)
+               return nil, wrapAzureError(err)
        }
 
-       instance_vhd := fmt.Sprintf("https://%s.blob.%s/%s/%s-os.vhd",
+       instanceVhd := fmt.Sprintf("https://%s.blob.%s/%s/%s-os.vhd",
                az.azconfig.StorageAccount,
                az.azureEnv.StorageEndpointSuffix,
                az.azconfig.BlobContainer,
@@ -382,10 +387,10 @@ echo '%s-%s' > /home/crunch/node-token`, name, newTags["node-token"])))
                                        Name:         to.StringPtr(name + "-os"),
                                        CreateOption: compute.FromImage,
                                        Image: &compute.VirtualHardDisk{
-                                               URI: to.StringPtr(string(imageId)),
+                                               URI: to.StringPtr(string(imageID)),
                                        },
                                        Vhd: &compute.VirtualHardDisk{
-                                               URI: &instance_vhd,
+                                               URI: &instanceVhd,
                                        },
                                },
                        },
@@ -418,40 +423,40 @@ echo '%s-%s' > /home/crunch/node-token`, name, newTags["node-token"])))
                },
        }
 
-       vm, err := az.vmClient.CreateOrUpdate(az.ctx, az.azconfig.ResourceGroup, name, vmParameters)
+       vm, err := az.vmClient.createOrUpdate(az.ctx, az.azconfig.ResourceGroup, name, vmParameters)
        if err != nil {
-               return nil, WrapAzureError(err)
+               return nil, wrapAzureError(err)
        }
 
-       return &AzureInstance{
+       return &azureInstance{
                provider: az,
                nic:      nic,
                vm:       vm,
        }, nil
 }
 
-func (az *AzureInstanceSet) Instances(InstanceTags) ([]Instance, error) {
+func (az *azureInstanceSet) Instances(cloud.InstanceTags) ([]cloud.Instance, error) {
        az.stopWg.Add(1)
        defer az.stopWg.Done()
 
-       interfaces, err := az.ManageNics()
+       interfaces, err := az.manageNics()
        if err != nil {
                return nil, err
        }
 
-       result, err := az.vmClient.ListComplete(az.ctx, az.azconfig.ResourceGroup)
+       result, err := az.vmClient.listComplete(az.ctx, az.azconfig.ResourceGroup)
        if err != nil {
-               return nil, WrapAzureError(err)
+               return nil, wrapAzureError(err)
        }
 
-       instances := make([]Instance, 0)
+       instances := make([]cloud.Instance, 0)
 
        for ; result.NotDone(); err = result.Next() {
                if err != nil {
-                       return nil, WrapAzureError(err)
+                       return nil, wrapAzureError(err)
                }
                if strings.HasPrefix(*result.Value().Name, az.namePrefix) {
-                       instances = append(instances, &AzureInstance{
+                       instances = append(instances, &azureInstance{
                                provider: az,
                                vm:       result.Value(),
                                nic:      interfaces[*(*result.Value().NetworkProfile.NetworkInterfaces)[0].ID]})
@@ -465,13 +470,13 @@ func (az *AzureInstanceSet) Instances(InstanceTags) ([]Instance, error) {
 // not associated with a virtual machine and have a "create-at" time
 // more than DeleteDanglingResourcesAfter (to prevent racing and
 // deleting newly created NICs) in the past are deleted.
-func (az *AzureInstanceSet) ManageNics() (map[string]network.Interface, error) {
+func (az *azureInstanceSet) manageNics() (map[string]network.Interface, error) {
        az.stopWg.Add(1)
        defer az.stopWg.Done()
 
-       result, err := az.netClient.ListComplete(az.ctx, az.azconfig.ResourceGroup)
+       result, err := az.netClient.listComplete(az.ctx, az.azconfig.ResourceGroup)
        if err != nil {
-               return nil, WrapAzureError(err)
+               return nil, wrapAzureError(err)
        }
 
        interfaces := make(map[string]network.Interface)
@@ -487,9 +492,9 @@ func (az *AzureInstanceSet) ManageNics() (map[string]network.Interface, error) {
                                interfaces[*result.Value().ID] = result.Value()
                        } else {
                                if result.Value().Tags["created-at"] != nil {
-                                       created_at, err := time.Parse(time.RFC3339Nano, *result.Value().Tags["created-at"])
+                                       createdAt, err := time.Parse(time.RFC3339Nano, *result.Value().Tags["created-at"])
                                        if err == nil {
-                                               if timestamp.Sub(created_at).Seconds() > az.azconfig.DeleteDanglingResourcesAfter {
+                                               if timestamp.Sub(createdAt).Seconds() > az.azconfig.DeleteDanglingResourcesAfter.Duration().Seconds() {
                                                        az.logger.Printf("Will delete %v because it is older than %v s", *result.Value().Name, az.azconfig.DeleteDanglingResourcesAfter)
                                                        az.deleteNIC <- *result.Value().Name
                                                }
@@ -506,7 +511,7 @@ func (az *AzureInstanceSet) ManageNics() (map[string]network.Interface, error) {
 // have "namePrefix", are "available" (which means they are not
 // leased to a VM) and haven't been modified for
 // DeleteDanglingResourcesAfter seconds.
-func (az *AzureInstanceSet) ManageBlobs() {
+func (az *azureInstanceSet) manageBlobs() {
        result, err := az.storageAcctClient.ListKeys(az.ctx, az.azconfig.ResourceGroup, az.azconfig.StorageAccount)
        if err != nil {
                az.logger.WithError(err).Warn("Couldn't get account keys")
@@ -537,7 +542,7 @@ func (az *AzureInstanceSet) ManageBlobs() {
                        if b.Properties.BlobType == storage.BlobTypePage &&
                                b.Properties.LeaseState == "available" &&
                                b.Properties.LeaseStatus == "unlocked" &&
-                               age.Seconds() > az.azconfig.DeleteDanglingResourcesAfter {
+                               age.Seconds() > az.azconfig.DeleteDanglingResourcesAfter.Duration().Seconds() {
 
                                az.logger.Printf("Blob %v is unlocked and not modified for %v seconds, will delete", b.Name, age.Seconds())
                                az.deleteBlob <- b
@@ -551,32 +556,32 @@ func (az *AzureInstanceSet) ManageBlobs() {
        }
 }
 
-func (az *AzureInstanceSet) Stop() {
+func (az *azureInstanceSet) Stop() {
        az.stopFunc()
        az.stopWg.Wait()
        close(az.deleteNIC)
        close(az.deleteBlob)
 }
 
-type AzureInstance struct {
-       provider *AzureInstanceSet
+type azureInstance struct {
+       provider *azureInstanceSet
        nic      network.Interface
        vm       compute.VirtualMachine
 }
 
-func (ai *AzureInstance) ID() InstanceID {
-       return InstanceID(*ai.vm.ID)
+func (ai *azureInstance) ID() cloud.InstanceID {
+       return cloud.InstanceID(*ai.vm.ID)
 }
 
-func (ai *AzureInstance) String() string {
+func (ai *azureInstance) String() string {
        return *ai.vm.Name
 }
 
-func (ai *AzureInstance) ProviderType() string {
+func (ai *azureInstance) ProviderType() string {
        return string(ai.vm.VirtualMachineProperties.HardwareProfile.VMSize)
 }
 
-func (ai *AzureInstance) SetTags(newTags InstanceTags) error {
+func (ai *azureInstance) SetTags(newTags cloud.InstanceTags) error {
        ai.provider.stopWg.Add(1)
        defer ai.provider.stopWg.Done()
 
@@ -596,16 +601,16 @@ func (ai *AzureInstance) SetTags(newTags InstanceTags) error {
                Location: &ai.provider.azconfig.Location,
                Tags:     tags,
        }
-       vm, err := ai.provider.vmClient.CreateOrUpdate(ai.provider.ctx, ai.provider.azconfig.ResourceGroup, *ai.vm.Name, vmParameters)
+       vm, err := ai.provider.vmClient.createOrUpdate(ai.provider.ctx, ai.provider.azconfig.ResourceGroup, *ai.vm.Name, vmParameters)
        if err != nil {
-               return WrapAzureError(err)
+               return wrapAzureError(err)
        }
        ai.vm = vm
 
        return nil
 }
 
-func (ai *AzureInstance) Tags() InstanceTags {
+func (ai *azureInstance) Tags() cloud.InstanceTags {
        tags := make(map[string]string)
 
        for k, v := range ai.vm.Tags {
@@ -617,19 +622,19 @@ func (ai *AzureInstance) Tags() InstanceTags {
        return tags
 }
 
-func (ai *AzureInstance) Destroy() error {
+func (ai *azureInstance) Destroy() error {
        ai.provider.stopWg.Add(1)
        defer ai.provider.stopWg.Done()
 
-       _, err := ai.provider.vmClient.Delete(ai.provider.ctx, ai.provider.azconfig.ResourceGroup, *ai.vm.Name)
-       return WrapAzureError(err)
+       _, err := ai.provider.vmClient.delete(ai.provider.ctx, ai.provider.azconfig.ResourceGroup, *ai.vm.Name)
+       return wrapAzureError(err)
 }
 
-func (ai *AzureInstance) Address() string {
+func (ai *azureInstance) Address() string {
        return *(*ai.nic.IPConfigurations)[0].PrivateIPAddress
 }
 
-func (ai *AzureInstance) VerifyHostKey(receivedKey ssh.PublicKey, client *ssh.Client) error {
+func (ai *azureInstance) VerifyHostKey(receivedKey ssh.PublicKey, client *ssh.Client) error {
        ai.provider.stopWg.Add(1)
        defer ai.provider.stopWg.Done()
 
@@ -641,9 +646,8 @@ func (ai *AzureInstance) VerifyHostKey(receivedKey ssh.PublicKey, client *ssh.Cl
        if tg != "" {
                if remoteFingerprint == tg {
                        return nil
-               } else {
-                       return fmt.Errorf("Key fingerprint did not match, expected %q got %q", tg, remoteFingerprint)
                }
+               return fmt.Errorf("Key fingerprint did not match, expected %q got %q", tg, remoteFingerprint)
        }
 
        nodetokenTag := tags["node-token"]
similarity index 68%
rename from lib/cloud/azure_test.go
rename to lib/cloud/azure/azure_test.go
index f74688bb180c98cb867eba92ce619a6f0f21b30e..850a3fb4270fd9c5da7b7829e9f00fa27c630a49 100644 (file)
@@ -3,31 +3,34 @@
 // SPDX-License-Identifier: AGPL-3.0
 //
 //
-// How to manually run individual tests against the real cloud
+// How to manually run individual tests against the real cloud:
 //
-// $ go test -v git.curoverse.com/arvados.git/lib/cloud -live-azure-cfg azconfig.yml -check.f=TestListInstances
+// $ go test -v git.curoverse.com/arvados.git/lib/cloud/azure -live-azure-cfg azconfig.yml -check.f=TestCreate
+//
+// Tests should be run individually and in the order they are listed in the file:
 //
 // Example azconfig.yml:
 //
-// subscription_id: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
-// key: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
-// region: centralus
-// cloud_environment: AzurePublicCloud
-// secret: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-// tenant_id: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
-// resource_group: zzzzz
-// network: zzzzz
-// subnet: zzzzz-subnet-private
-// storage_account: example
-// blob_container: vhds
-// image: "https://example.blob.core.windows.net/system/Microsoft.Compute/Images/images/zzzzz-compute-osDisk.XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX.vhd"
-// delete_dangling_resources_after: 20
-// authorized_key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDLQS1ExT2+WjA0d/hntEAyAtgeN1W2ik2QX8c2zO6HjlPHWXL92r07W0WMuDib40Pcevpi1BXeBWXA9ZB5KKMJB+ukaAu22KklnQuUmNvk6ZXnPKSkGxuCYvPQb08WhHf3p1VxiKfP3iauedBDM4x9/bkJohlBBQiFXzNUcQ+a6rKiMzmJN2gbL8ncyUzc+XQ5q4JndTwTGtOlzDiGOc9O4z5Dd76wtAVJneOuuNpwfFRVHThpJM6VThpCZOnl8APaceWXKeuwOuCae3COZMz++xQfxOfZ9Z8aIwo+TlQhsRaNfZ4Vjrop6ej8dtfZtgUFKfbXEOYaHrGrWGotFDTD example@example"
-
-package cloud
+// ImageIDForTestSuite: "https://example.blob.core.windows.net/system/Microsoft.Compute/Images/images/zzzzz-compute-osDisk.XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX.vhd"
+// DriverParameters:
+//      SubscriptionID: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
+//      ClientID: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
+//      Location: centralus
+//      CloudEnvironment: AzurePublicCloud
+//      ClientSecret: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+//      TenantId: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
+//      ResourceGroup: zzzzz
+//      Network: zzzzz
+//      Subnet: zzzzz-subnet-private
+//      StorageAccount: example
+//      BlobContainer: vhds
+//      DeleteDanglingResourcesAfter: 20s
+
+package azure
 
 import (
        "context"
+       "encoding/json"
        "errors"
        "flag"
        "io/ioutil"
@@ -35,8 +38,10 @@ import (
        "net"
        "net/http"
        "os"
+       "testing"
        "time"
 
+       "git.curoverse.com/arvados.git/lib/cloud"
        "git.curoverse.com/arvados.git/sdk/go/arvados"
        "git.curoverse.com/arvados.git/sdk/go/config"
        "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-06-01/compute"
@@ -51,15 +56,20 @@ import (
        check "gopkg.in/check.v1"
 )
 
+// Gocheck boilerplate
+func Test(t *testing.T) {
+       check.TestingT(t)
+}
+
 type AzureInstanceSetSuite struct{}
 
 var _ = check.Suite(&AzureInstanceSetSuite{})
 
 type VirtualMachinesClientStub struct{}
 
-var testKey []byte = []byte(`ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDLQS1ExT2+WjA0d/hntEAyAtgeN1W2ik2QX8c2zO6HjlPHWXL92r07W0WMuDib40Pcevpi1BXeBWXA9ZB5KKMJB+ukaAu22KklnQuUmNvk6ZXnPKSkGxuCYvPQb08WhHf3p1VxiKfP3iauedBDM4x9/bkJohlBBQiFXzNUcQ+a6rKiMzmJN2gbL8ncyUzc+XQ5q4JndTwTGtOlzDiGOc9O4z5Dd76wtAVJneOuuNpwfFRVHThpJM6VThpCZOnl8APaceWXKeuwOuCae3COZMz++xQfxOfZ9Z8aIwo+TlQhsRaNfZ4Vjrop6ej8dtfZtgUFKfbXEOYaHrGrWGotFDTD example@example`)
+var testKey = []byte(`ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDLQS1ExT2+WjA0d/hntEAyAtgeN1W2ik2QX8c2zO6HjlPHWXL92r07W0WMuDib40Pcevpi1BXeBWXA9ZB5KKMJB+ukaAu22KklnQuUmNvk6ZXnPKSkGxuCYvPQb08WhHf3p1VxiKfP3iauedBDM4x9/bkJohlBBQiFXzNUcQ+a6rKiMzmJN2gbL8ncyUzc+XQ5q4JndTwTGtOlzDiGOc9O4z5Dd76wtAVJneOuuNpwfFRVHThpJM6VThpCZOnl8APaceWXKeuwOuCae3COZMz++xQfxOfZ9Z8aIwo+TlQhsRaNfZ4Vjrop6ej8dtfZtgUFKfbXEOYaHrGrWGotFDTD example@example`)
 
-func (*VirtualMachinesClientStub) CreateOrUpdate(ctx context.Context,
+func (*VirtualMachinesClientStub) createOrUpdate(ctx context.Context,
        resourceGroupName string,
        VMName string,
        parameters compute.VirtualMachine) (result compute.VirtualMachine, err error) {
@@ -68,17 +78,17 @@ func (*VirtualMachinesClientStub) CreateOrUpdate(ctx context.Context,
        return parameters, nil
 }
 
-func (*VirtualMachinesClientStub) Delete(ctx context.Context, resourceGroupName string, VMName string) (result *http.Response, err error) {
+func (*VirtualMachinesClientStub) delete(ctx context.Context, resourceGroupName string, VMName string) (result *http.Response, err error) {
        return nil, nil
 }
 
-func (*VirtualMachinesClientStub) ListComplete(ctx context.Context, resourceGroupName string) (result compute.VirtualMachineListResultIterator, err error) {
+func (*VirtualMachinesClientStub) listComplete(ctx context.Context, resourceGroupName string) (result compute.VirtualMachineListResultIterator, err error) {
        return compute.VirtualMachineListResultIterator{}, nil
 }
 
 type InterfacesClientStub struct{}
 
-func (*InterfacesClientStub) CreateOrUpdate(ctx context.Context,
+func (*InterfacesClientStub) createOrUpdate(ctx context.Context,
        resourceGroupName string,
        nicName string,
        parameters network.Interface) (result network.Interface, err error) {
@@ -87,17 +97,22 @@ func (*InterfacesClientStub) CreateOrUpdate(ctx context.Context,
        return parameters, nil
 }
 
-func (*InterfacesClientStub) Delete(ctx context.Context, resourceGroupName string, VMName string) (result *http.Response, err error) {
+func (*InterfacesClientStub) delete(ctx context.Context, resourceGroupName string, VMName string) (result *http.Response, err error) {
        return nil, nil
 }
 
-func (*InterfacesClientStub) ListComplete(ctx context.Context, resourceGroupName string) (result network.InterfaceListResultIterator, err error) {
+func (*InterfacesClientStub) listComplete(ctx context.Context, resourceGroupName string) (result network.InterfaceListResultIterator, err error) {
        return network.InterfaceListResultIterator{}, nil
 }
 
+type testConfig struct {
+       ImageIDForTestSuite string
+       DriverParameters    json.RawMessage
+}
+
 var live = flag.String("live-azure-cfg", "", "Test with real azure API, provide config file")
 
-func GetInstanceSet() (InstanceSet, ImageID, arvados.Cluster, error) {
+func GetInstanceSet() (cloud.InstanceSet, cloud.ImageID, arvados.Cluster, error) {
        cluster := arvados.Cluster{
                InstanceTypes: arvados.InstanceTypeMap(map[string]arvados.InstanceType{
                        "tiny": arvados.InstanceType{
@@ -111,29 +126,29 @@ func GetInstanceSet() (InstanceSet, ImageID, arvados.Cluster, error) {
                        },
                })}
        if *live != "" {
-               cfg := make(map[string]interface{})
-               err := config.LoadFile(&cfg, *live)
+               var exampleCfg testConfig
+               err := config.LoadFile(&exampleCfg, *live)
                if err != nil {
-                       return nil, ImageID(""), cluster, err
+                       return nil, cloud.ImageID(""), cluster, err
                }
-               ap, err := NewAzureInstanceSet(cfg, "test123", logrus.StandardLogger())
-               return ap, ImageID(cfg["image"].(string)), cluster, err
-       } else {
-               ap := AzureInstanceSet{
-                       azconfig: AzureInstanceSetConfig{
-                               BlobContainer: "vhds",
-                       },
-                       dispatcherID: "test123",
-                       namePrefix:   "compute-test123-",
-                       logger:       logrus.StandardLogger(),
-                       deleteNIC:    make(chan string),
-                       deleteBlob:   make(chan storage.Blob),
-               }
-               ap.ctx, ap.stopFunc = context.WithCancel(context.Background())
-               ap.vmClient = &VirtualMachinesClientStub{}
-               ap.netClient = &InterfacesClientStub{}
-               return &ap, ImageID("blob"), cluster, nil
+
+               ap, err := newAzureInstanceSet(exampleCfg.DriverParameters, "test123", logrus.StandardLogger())
+               return ap, cloud.ImageID(exampleCfg.ImageIDForTestSuite), cluster, err
+       }
+       ap := azureInstanceSet{
+               azconfig: azureInstanceSetConfig{
+                       BlobContainer: "vhds",
+               },
+               dispatcherID: "test123",
+               namePrefix:   "compute-test123-",
+               logger:       logrus.StandardLogger(),
+               deleteNIC:    make(chan string),
+               deleteBlob:   make(chan storage.Blob),
        }
+       ap.ctx, ap.stopFunc = context.WithCancel(context.Background())
+       ap.vmClient = &VirtualMachinesClientStub{}
+       ap.netClient = &InterfacesClientStub{}
+       return &ap, cloud.ImageID("blob"), cluster, nil
 }
 
 func (*AzureInstanceSetSuite) TestCreate(c *check.C) {
@@ -182,7 +197,7 @@ func (*AzureInstanceSetSuite) TestManageNics(c *check.C) {
                c.Fatal("Error making provider", err)
        }
 
-       ap.(*AzureInstanceSet).ManageNics()
+       ap.(*azureInstanceSet).manageNics()
        ap.Stop()
 }
 
@@ -192,7 +207,7 @@ func (*AzureInstanceSetSuite) TestManageBlobs(c *check.C) {
                c.Fatal("Error making provider", err)
        }
 
-       ap.(*AzureInstanceSet).ManageBlobs()
+       ap.(*azureInstanceSet).manageBlobs()
        ap.Stop()
 }
 
@@ -216,7 +231,7 @@ func (*AzureInstanceSetSuite) TestDeleteFake(c *check.C) {
                c.Fatal("Error making provider", err)
        }
 
-       _, err = ap.(*AzureInstanceSet).netClient.Delete(context.Background(), "fakefakefake", "fakefakefake")
+       _, err = ap.(*azureInstanceSet).netClient.delete(context.Background(), "fakefakefake", "fakefakefake")
 
        de, ok := err.(autorest.DetailedError)
        if ok {
@@ -238,8 +253,8 @@ func (*AzureInstanceSetSuite) TestWrapError(c *check.C) {
                        ServiceError: &azure.ServiceError{},
                },
        }
-       wrapped := WrapAzureError(retryError)
-       _, ok := wrapped.(RateLimitError)
+       wrapped := wrapAzureError(retryError)
+       _, ok := wrapped.(cloud.RateLimitError)
        c.Check(ok, check.Equals, true)
 
        quotaError := autorest.DetailedError{
@@ -254,8 +269,8 @@ func (*AzureInstanceSetSuite) TestWrapError(c *check.C) {
                        },
                },
        }
-       wrapped = WrapAzureError(quotaError)
-       _, ok = wrapped.(QuotaError)
+       wrapped = wrapAzureError(quotaError)
+       _, ok = wrapped.(cloud.QuotaError)
        c.Check(ok, check.Equals, true)
 }
 
@@ -307,7 +322,7 @@ func (*AzureInstanceSetSuite) TestSSH(c *check.C) {
        }
 }
 
-func SetupSSHClient(c *check.C, inst Instance) (*ssh.Client, error) {
+func SetupSSHClient(c *check.C, inst cloud.Instance) (*ssh.Client, error) {
        addr := inst.Address() + ":2222"
        if addr == "" {
                return nil, errors.New("instance has no address")
diff --git a/lib/cloud/gocheck_test.go b/lib/cloud/gocheck_test.go
deleted file mode 100644 (file)
index d839268..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-package cloud
-
-import (
-       "testing"
-
-       check "gopkg.in/check.v1"
-)
-
-// Gocheck boilerplate
-func Test(t *testing.T) {
-       check.TestingT(t)
-}
index 969a4bc2ddeb4b6389bf80912f9826009a56400c..46a2c1682404ec067bc7c332a77c469f0b353d57 100644 (file)
@@ -5,6 +5,7 @@
 package cloud
 
 import (
+       "encoding/json"
        "io"
        "time"
 
@@ -153,9 +154,9 @@ type InstanceSet interface {
 //
 //     type exampleDriver struct {}
 //
-//     func (*exampleDriver) InstanceSet(config map[string]interface{}, id InstanceSetID) (InstanceSet, error) {
+//     func (*exampleDriver) InstanceSet(config json.RawMessage, id InstanceSetID) (InstanceSet, error) {
 //             var is exampleInstanceSet
-//             if err := mapstructure.Decode(config, &is); err != nil {
+//             if err := json.Unmarshal(config, &is); err != nil {
 //                     return nil, err
 //             }
 //             is.ownID = id
@@ -164,17 +165,17 @@ type InstanceSet interface {
 //
 //     var _ = registerCloudDriver("example", &exampleDriver{})
 type Driver interface {
-       InstanceSet(config map[string]interface{}, id InstanceSetID, logger logrus.FieldLogger) (InstanceSet, error)
+       InstanceSet(config json.RawMessage, id InstanceSetID, logger logrus.FieldLogger) (InstanceSet, error)
 }
 
 // DriverFunc makes a Driver using the provided function as its
 // InstanceSet method. This is similar to http.HandlerFunc.
-func DriverFunc(fn func(config map[string]interface{}, id InstanceSetID, logger logrus.FieldLogger) (InstanceSet, error)) Driver {
+func DriverFunc(fn func(config json.RawMessage, id InstanceSetID, logger logrus.FieldLogger) (InstanceSet, error)) Driver {
        return driverFunc(fn)
 }
 
-type driverFunc func(config map[string]interface{}, id InstanceSetID, logger logrus.FieldLogger) (InstanceSet, error)
+type driverFunc func(config json.RawMessage, id InstanceSetID, logger logrus.FieldLogger) (InstanceSet, error)
 
-func (df driverFunc) InstanceSet(config map[string]interface{}, id InstanceSetID, logger logrus.FieldLogger) (InstanceSet, error) {
+func (df driverFunc) InstanceSet(config json.RawMessage, id InstanceSetID, logger logrus.FieldLogger) (InstanceSet, error) {
        return df(config, id, logger)
 }
index c935e20be6b29d90577640d643d74c73fa75c84e..d49d16a35eee1be2fe3cbe0f765921316fdafa64 100644 (file)
@@ -555,16 +555,20 @@ func (s *FederationSuite) TestGetRemoteContainerRequest(c *check.C) {
 
 func (s *FederationSuite) TestUpdateRemoteContainerRequest(c *check.C) {
        defer s.localServiceReturns404(c).Close()
-       req := httptest.NewRequest("PATCH", "/arvados/v1/container_requests/"+arvadostest.QueuedContainerRequestUUID,
-               strings.NewReader(`{"container_request": {"priority": 696}}`))
-       req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
-       req.Header.Set("Content-type", "application/json")
-       resp := s.testRequest(req)
-       c.Check(resp.StatusCode, check.Equals, http.StatusOK)
-       var cr arvados.ContainerRequest
-       c.Check(json.NewDecoder(resp.Body).Decode(&cr), check.IsNil)
-       c.Check(cr.UUID, check.Equals, arvadostest.QueuedContainerRequestUUID)
-       c.Check(cr.Priority, check.Equals, 696)
+       setPri := func(pri int) {
+               req := httptest.NewRequest("PATCH", "/arvados/v1/container_requests/"+arvadostest.QueuedContainerRequestUUID,
+                       strings.NewReader(fmt.Sprintf(`{"container_request": {"priority": %d}}`, pri)))
+               req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
+               req.Header.Set("Content-type", "application/json")
+               resp := s.testRequest(req)
+               c.Check(resp.StatusCode, check.Equals, http.StatusOK)
+               var cr arvados.ContainerRequest
+               c.Check(json.NewDecoder(resp.Body).Decode(&cr), check.IsNil)
+               c.Check(cr.UUID, check.Equals, arvadostest.QueuedContainerRequestUUID)
+               c.Check(cr.Priority, check.Equals, pri)
+       }
+       setPri(696)
+       setPri(1) // Reset fixture so side effect doesn't break other tests.
 }
 
 func (s *FederationSuite) TestCreateRemoteContainerRequest(c *check.C) {
index 295dde7ca42821b1c8f904eec42ac7e7764812fa..53125ae5543b51287e5de80a8b442f2002972a86 100644 (file)
@@ -80,12 +80,10 @@ func (h *Handler) setup() {
        h.handlerStack = mux
 
        sc := *arvados.DefaultSecureClient
-       sc.Timeout = time.Duration(h.Cluster.HTTPRequestTimeout)
        sc.CheckRedirect = neverRedirect
        h.secureClient = &sc
 
        ic := *arvados.InsecureHTTPClient
-       ic.Timeout = time.Duration(h.Cluster.HTTPRequestTimeout)
        ic.CheckRedirect = neverRedirect
        h.insecureClient = &ic
 
index 847fe9e27c03e3794f068bd59c7c13031798fb71..297782c35b9a972668fd7623d251e3411b26389b 100644 (file)
@@ -223,8 +223,25 @@ func (cq *Queue) addEnt(uuid string, ctr arvados.Container) {
                // error: it wouldn't help to try again, or to leave
                // it for a different dispatcher process to attempt.
                errorString := err.Error()
-               cq.logger.WithField("ContainerUUID", ctr.UUID).Warn("cancel container with no suitable instance type")
+               logger := cq.logger.WithField("ContainerUUID", ctr.UUID)
+               logger.WithError(err).Warn("cancel container with no suitable instance type")
                go func() {
+                       if ctr.State == arvados.ContainerStateQueued {
+                               // Can't set runtime error without
+                               // locking first. If Lock() is
+                               // successful, it will call addEnt()
+                               // again itself, and we'll fall
+                               // through to the
+                               // setRuntimeError/Cancel code below.
+                               err := cq.Lock(ctr.UUID)
+                               if err != nil {
+                                       logger.WithError(err).Warn("lock failed")
+                                       // ...and try again on the
+                                       // next Update, if the problem
+                                       // still exists.
+                               }
+                               return
+                       }
                        var err error
                        defer func() {
                                if err == nil {
@@ -239,14 +256,8 @@ func (cq *Queue) addEnt(uuid string, ctr arvados.Container) {
                                if latest.State == arvados.ContainerStateCancelled {
                                        return
                                }
-                               cq.logger.WithField("ContainerUUID", ctr.UUID).WithError(err).Warn("error while trying to cancel unsatisfiable container")
+                               logger.WithError(err).Warn("error while trying to cancel unsatisfiable container")
                        }()
-                       if ctr.State == arvados.ContainerStateQueued {
-                               err = cq.Lock(ctr.UUID)
-                               if err != nil {
-                                       return
-                               }
-                       }
                        err = cq.setRuntimeError(ctr.UUID, errorString)
                        if err != nil {
                                return
index a6e62e05bb1a4cb611d6698812ddc459585f24fe..2ac69e04c17bb94ce706979547c8501b3f80b609 100644 (file)
@@ -8,12 +8,13 @@ import (
        "fmt"
 
        "git.curoverse.com/arvados.git/lib/cloud"
+       "git.curoverse.com/arvados.git/lib/cloud/azure"
        "git.curoverse.com/arvados.git/sdk/go/arvados"
        "github.com/sirupsen/logrus"
 )
 
 var drivers = map[string]cloud.Driver{
-       "azure": cloud.DriverFunc(cloud.NewAzureInstanceSet),
+       "azure": azure.Driver,
 }
 
 func newInstanceSet(cluster *arvados.Cluster, setID cloud.InstanceSetID, logger logrus.FieldLogger) (cloud.InstanceSet, error) {
index a2231673fcd08c2230259b915adc41a8f9a3ed58..fbab30b175d674ceec0b18d2b1726dfd930df1d7 100644 (file)
@@ -6,6 +6,7 @@ package test
 
 import (
        "crypto/rand"
+       "encoding/json"
        "errors"
        "fmt"
        "io"
@@ -17,7 +18,6 @@ import (
 
        "git.curoverse.com/arvados.git/lib/cloud"
        "git.curoverse.com/arvados.git/sdk/go/arvados"
-       "github.com/mitchellh/mapstructure"
        "github.com/sirupsen/logrus"
        "golang.org/x/crypto/ssh"
 )
@@ -55,7 +55,7 @@ type StubDriver struct {
 }
 
 // InstanceSet returns a new *StubInstanceSet.
-func (sd *StubDriver) InstanceSet(params map[string]interface{}, id cloud.InstanceSetID, logger logrus.FieldLogger) (cloud.InstanceSet, error) {
+func (sd *StubDriver) InstanceSet(params json.RawMessage, id cloud.InstanceSetID, logger logrus.FieldLogger) (cloud.InstanceSet, error) {
        if sd.holdCloudOps == nil {
                sd.holdCloudOps = make(chan bool)
        }
@@ -64,7 +64,12 @@ func (sd *StubDriver) InstanceSet(params map[string]interface{}, id cloud.Instan
                servers: map[cloud.InstanceID]*StubVM{},
        }
        sd.instanceSets = append(sd.instanceSets, &sis)
-       return &sis, mapstructure.Decode(params, &sis)
+
+       var err error
+       if params != nil {
+               err = json.Unmarshal(params, &sis)
+       }
+       return &sis, err
 }
 
 // InstanceSets returns all instances that have been created by the
index 5491200cb39350b8ad9dcb79712b0e8bd93ed90a..636cf350c151b150c59a96c45c312ea7af6d45c2 100644 (file)
@@ -143,7 +143,7 @@ type CloudVMs struct {
        ImageID string
 
        Driver           string
-       DriverParameters map[string]interface{}
+       DriverParameters json.RawMessage
 }
 
 type InstanceTypeMap map[string]InstanceType
index d2a19a024c1aaecb9150d998bc79e6f3281a7b1b..25eed010f26c534ef8e36dfa119065731d1e2ac4 100644 (file)
@@ -14,7 +14,7 @@ import (
 // a number of nanoseconds.
 type Duration time.Duration
 
-// UnmarshalJSON implements json.Unmarshaler
+// UnmarshalJSON implements json.Unmarshaler.
 func (d *Duration) UnmarshalJSON(data []byte) error {
        if data[0] == '"' {
                return d.Set(string(data[1 : len(data)-1]))
@@ -22,22 +22,22 @@ func (d *Duration) UnmarshalJSON(data []byte) error {
        return fmt.Errorf("duration must be given as a string like \"600s\" or \"1h30m\"")
 }
 
-// MarshalJSON implements json.Marshaler
+// MarshalJSON implements json.Marshaler.
 func (d *Duration) MarshalJSON() ([]byte, error) {
        return json.Marshal(d.String())
 }
 
-// String implements fmt.Stringer
+// String implements fmt.Stringer.
 func (d Duration) String() string {
        return time.Duration(d).String()
 }
 
-// Duration returns a time.Duration
+// Duration returns a time.Duration.
 func (d Duration) Duration() time.Duration {
        return time.Duration(d)
 }
 
-// Value implements flag.Value
+// Set implements the flag.Value interface and sets the duration value by using time.ParseDuration to parse the string.
 func (d *Duration) Set(s string) error {
        dur, err := time.ParseDuration(s)
        *d = Duration(dur)
index da4c39d9014431fe750a78fa3c1bdb07232a0bff..2cda8bcb1441967135b1feb5c2adf344e970da81 100644 (file)
@@ -30,3 +30,6 @@
 
 # Generated git-commit.version file
 /git-commit.version
+
+# Generated when building distribution packages
+/package-build.version
index 33cc686d4f5a14f3a432bc3df077ab258797a4a8..6147b79f9f5aa16c6b9e24ef5164bab43139373a 100644 (file)
@@ -262,6 +262,7 @@ class Collection < ArvadosModel
       sync_past_versions if syncable_updates.any?
       if snapshot
         snapshot.attributes = self.syncable_updates
+        snapshot.manifest_text = snapshot.signed_manifest_text
         snapshot.save
       end
     end
index bd586907ee2eaf205616251be126bc7cf9c94b09..3d1e6491150624bcbcbad9aa95a5e4dc202dd390 100644 (file)
@@ -346,7 +346,7 @@ class Container < ArvadosModel
     transaction do
       reload
       check_lock_fail
-      update_attributes!(state: Locked)
+      update_attributes!(state: Locked, lock_count: self.lock_count+1)
     end
   end
 
@@ -364,7 +364,14 @@ class Container < ArvadosModel
     transaction do
       reload(lock: 'FOR UPDATE')
       check_unlock_fail
-      update_attributes!(state: Queued)
+      if self.lock_count < Rails.configuration.max_container_dispatch_attempts
+        update_attributes!(state: Queued)
+      else
+        update_attributes!(state: Cancelled,
+                           runtime_status: {
+                             error: "Container exceeded 'max_container_dispatch_attempts' (lock_count=#{self.lock_count}."
+                           })
+      end
     end
   end
 
@@ -454,7 +461,7 @@ class Container < ArvadosModel
 
     case self.state
     when Locked
-      permitted.push :priority, :runtime_status, :log
+      permitted.push :priority, :runtime_status, :log, :lock_count
 
     when Queued
       permitted.push :priority
@@ -475,7 +482,7 @@ class Container < ArvadosModel
       when Running
         permitted.push :finished_at, *progress_attrs
       when Queued, Locked
-        permitted.push :finished_at, :log
+        permitted.push :finished_at, :log, :runtime_status
       end
 
     else
index dcf270e3fb5d1a59a25e9858fc65e2eb2b901c42..d0f3a4caeb11d9f931772a8b1036fb257c2632fe 100644 (file)
@@ -529,6 +529,10 @@ common:
   # > 0 = auto-create a new version when older than the specified number of seconds.
   preserve_version_if_idle: -1
 
+  # Number of times a container can be unlocked before being
+  # automatically cancelled.
+  max_container_dispatch_attempts: 5
+
 development:
   force_ssl: false
   cache_classes: false
diff --git a/services/api/db/migrate/20190214214814_add_container_lock_count.rb b/services/api/db/migrate/20190214214814_add_container_lock_count.rb
new file mode 100644 (file)
index 0000000..a496eb0
--- /dev/null
@@ -0,0 +1,5 @@
+class AddContainerLockCount < ActiveRecord::Migration
+  def change
+    add_column :containers, :lock_count, :int, :null => false, :default => 0
+  end
+end
index 211fa5043fda2aedc33646f8c98dff863bec8d7a..f766f33e1b35e1f85a64aa2c5d87bf85e2bb6d0f 100644 (file)
@@ -362,7 +362,8 @@ CREATE TABLE public.containers (
     runtime_status jsonb DEFAULT '{}'::jsonb,
     runtime_user_uuid text,
     runtime_auth_scopes jsonb,
-    runtime_token text
+    runtime_token text,
+    lock_count integer DEFAULT 0 NOT NULL
 );
 
 
@@ -3217,3 +3218,5 @@ INSERT INTO schema_migrations (version) VALUES ('20181011184200');
 
 INSERT INTO schema_migrations (version) VALUES ('20181213183234');
 
+INSERT INTO schema_migrations (version) VALUES ('20190214214814');
+
index 26b8290e6961452e97f505ad3b239f6ef5a28596..997d89d5cd13d72c97dd3edcaa177bca36e1efed 100644 (file)
@@ -1279,7 +1279,6 @@ EOS
         version: 42,
         current_version_uuid: collections(:collection_owned_by_active).uuid,
         manifest_text: manifest_text,
-        # portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47"
       }
     }
     assert_response :success
@@ -1287,4 +1286,28 @@ EOS
     assert_equal 1, resp['version']
     assert_equal resp['uuid'], resp['current_version_uuid']
   end
+
+  test "update collection with versioning enabled" do
+    Rails.configuration.collection_versioning = true
+    Rails.configuration.preserve_version_if_idle = 1 # 1 second
+
+    col = collections(:collection_owned_by_active)
+    assert_equal 2, col.version
+    assert col.modified_at < Time.now - 1.second
+
+    token = api_client_authorizations(:active).v2token
+    signed = Blob.sign_locator(
+      'acbd18db4cc2f85cedef654fccc4a4d8+3',
+      key: Rails.configuration.blob_signing_key,
+      api_token: token)
+    authorize_with_token token
+    put :update, {
+          id: col.uuid,
+          collection: {
+            manifest_text: ". #{signed} 0:3:foo.txt\n",
+          },
+        }
+    assert_response :success
+    assert_equal 3, json_response['version']
+  end
 end
index 2a9ff5bf4cc6985a413f62a03d7b9555e9c0f938..5750d5ebbd0394d99bdce8ae2a038bb1d799cd77 100644 (file)
@@ -663,6 +663,52 @@ class ContainerTest < ActiveSupport::TestCase
     assert_operator auth_exp, :<, db_current_time
   end
 
+  test "Exceed maximum lock-unlock cycles" do
+    Rails.configuration.max_container_dispatch_attempts = 3
+
+    set_user_from_auth :active
+    c, cr = minimal_new
+
+    set_user_from_auth :dispatch1
+    assert_equal Container::Queued, c.state
+    assert_equal 0, c.lock_count
+
+    c.lock
+    c.reload
+    assert_equal 1, c.lock_count
+    assert_equal Container::Locked, c.state
+
+    c.unlock
+    c.reload
+    assert_equal 1, c.lock_count
+    assert_equal Container::Queued, c.state
+
+    c.lock
+    c.reload
+    assert_equal 2, c.lock_count
+    assert_equal Container::Locked, c.state
+
+    c.unlock
+    c.reload
+    assert_equal 2, c.lock_count
+    assert_equal Container::Queued, c.state
+
+    c.lock
+    c.reload
+    assert_equal 3, c.lock_count
+    assert_equal Container::Locked, c.state
+
+    c.unlock
+    c.reload
+    assert_equal 3, c.lock_count
+    assert_equal Container::Cancelled, c.state
+
+    assert_raise(ArvadosModel::LockFailedError) do
+      # Cancelled to Locked is not allowed
+      c.lock
+    end
+  end
+
   test "Container queued cancel" do
     set_user_from_auth :active
     c, cr = minimal_new({container_count_max: 1})
index 1b24804b056e2cb2caef666ae695649d935613cd..db69f9fa46832dc36aff1ef20bd176fb7d5c22e5 100644 (file)
                        "revision": "b8bc1bf767474819792c23f32d8286a45736f1c6",
                        "revisionTime": "2016-12-03T19:45:07Z"
                },
-               {
-                       "checksumSHA1": "ewGq4nGalpCQOHcmBTdAEQx1wW0=",
-                       "path": "github.com/mitchellh/mapstructure",
-                       "revision": "bb74f1db0675b241733089d5a1faa5dd8b0ef57b",
-                       "revisionTime": "2018-05-11T14:21:26Z"
-               },
                {
                        "checksumSHA1": "OFNit1Qx2DdWhotfREKodDNUwCM=",
                        "path": "github.com/opencontainers/go-digest",