Merge branch '14745-azure-cloud-driver-fixups'
authorEric Biagiotti <ebiagiotti@veritasgenetcs.com>
Thu, 14 Feb 2019 19:56:47 +0000 (14:56 -0500)
committerEric Biagiotti <ebiagiotti@veritasgenetcs.com>
Thu, 14 Feb 2019 19:56:47 +0000 (14:56 -0500)
refs #14745

Arvados-DCO-1.1-Signed-off-by: Eric Biagiotti <eric.biagiotti@gmail.com>

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/dispatchcloud/driver.go
lib/dispatchcloud/test/stub_driver.go
sdk/go/arvados/config.go
sdk/go/arvados/duration.go
vendor/vendor.json

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 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 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",