"github.com/Azure/go-autorest/autorest/azure/auth"
"github.com/Azure/go-autorest/autorest/to"
"github.com/jmcvetta/randutil"
+ "golang.org/x/crypto/ssh"
)
type AzureProviderConfig struct {
StorageAccount string `json:"storage_account"`
BlobContainer string `json:"blob_container"`
Image string `json:"image"`
- AuthorizedKey string `json:"authorized_key"`
DeleteDanglingResourcesAfter float64 `json:"delete_dangling_resources_after"`
}
type AzureProvider struct {
azconfig AzureProviderConfig
- arvconfig arvados.Cluster
vmClient VirtualMachinesClientWrapper
netClient InterfacesClientWrapper
storageAcctClient storageacct.AccountsClient
namePrefix string
}
-func NewAzureProvider(azcfg AzureProviderConfig, arvcfg arvados.Cluster, dispatcherID string) (prv Provider, err error) {
+func NewAzureProvider(azcfg AzureProviderConfig, dispatcherID string) (prv InstanceProvider, err error) {
ap := AzureProvider{}
- err = ap.setup(azcfg, arvcfg, dispatcherID)
+ err = ap.setup(azcfg, dispatcherID)
if err != nil {
return nil, err
}
return &ap, nil
}
-func (az *AzureProvider) setup(azcfg AzureProviderConfig, arvcfg arvados.Cluster, dispatcherID string) (err error) {
+func (az *AzureProvider) setup(azcfg AzureProviderConfig, dispatcherID string) (err error) {
az.azconfig = azcfg
- az.arvconfig = arvcfg
vmClient := compute.NewVirtualMachinesClient(az.azconfig.SubscriptionID)
netClient := network.NewInterfacesClient(az.azconfig.SubscriptionID)
storageAcctClient := storageacct.NewAccountsClient(az.azconfig.SubscriptionID)
func (az *AzureProvider) Create(ctx context.Context,
instanceType arvados.InstanceType,
imageId ImageID,
- newTags InstanceTags) (Instance, error) {
+ newTags InstanceTags,
+ publicKey ssh.PublicKey) (Instance, error) {
name, err := randutil.String(15, "abcdefghijklmnopqrstuvwxyz0123456789")
if err != nil {
log.Printf("URI instance vhd %v", instance_vhd)
- tags["arvados-instance-type"] = &instanceType.Name
-
vmParameters := compute.VirtualMachine{
Location: &az.azconfig.Location,
Tags: tags,
},
OsProfile: &compute.OSProfile{
ComputerName: &name,
- AdminUsername: to.StringPtr("arvados"),
+ AdminUsername: to.StringPtr("crunch"),
LinuxConfiguration: &compute.LinuxConfiguration{
DisablePasswordAuthentication: to.BoolPtr(true),
SSH: &compute.SSHConfiguration{
PublicKeys: &[]compute.SSHPublicKey{
compute.SSHPublicKey{
- Path: to.StringPtr("/home/arvados/.ssh/authorized_keys"),
- KeyData: to.StringPtr(az.azconfig.AuthorizedKey),
+ Path: to.StringPtr("/home/crunch/.ssh/authorized_keys"),
+ KeyData: to.StringPtr(string(ssh.MarshalAuthorizedKey(publicKey))),
},
},
},
}
return &AzureInstance{
- instanceType: instanceType,
- provider: az,
- nic: nic,
- vm: vm,
+ provider: az,
+ nic: nic,
+ vm: vm,
}, nil
}
if err != nil {
return nil, WrapAzureError(err)
}
- if strings.HasPrefix(*result.Value().Name, az.namePrefix) &&
- result.Value().Tags["arvados-instance-type"] != nil {
+ if strings.HasPrefix(*result.Value().Name, az.namePrefix) {
instances = append(instances, &AzureInstance{
- provider: az,
- vm: result.Value(),
- nic: interfaces[*(*result.Value().NetworkProfile.NetworkInterfaces)[0].ID],
- instanceType: az.arvconfig.InstanceTypes[(*result.Value().Tags["arvados-instance-type"])]})
+ provider: az,
+ vm: result.Value(),
+ nic: interfaces[*(*result.Value().NetworkProfile.NetworkInterfaces)[0].ID]})
}
}
return instances, nil
}
}
+func (az *AzureProvider) Stop() {
+}
+
type AzureInstance struct {
- instanceType arvados.InstanceType
- provider *AzureProvider
- nic network.Interface
- vm compute.VirtualMachine
+ provider *AzureProvider
+ nic network.Interface
+ vm compute.VirtualMachine
}
-func (ai *AzureInstance) String() string {
- return *ai.vm.Name
+func (ai *AzureInstance) ID() InstanceID {
+ return InstanceID(*ai.vm.ID)
}
-func (ai *AzureInstance) InstanceType() arvados.InstanceType {
- return ai.instanceType
+func (ai *AzureInstance) String() string {
+ return *ai.vm.Name
}
func (ai *AzureInstance) SetTags(ctx context.Context, newTags InstanceTags) error {
return nil
}
-func (ai *AzureInstance) GetTags(ctx context.Context) (InstanceTags, error) {
+func (ai *AzureInstance) Tags(ctx context.Context) (InstanceTags, error) {
tags := make(map[string]string)
for k, v := range ai.vm.Tags {
import (
"context"
+ "errors"
"flag"
+ "io/ioutil"
"log"
+ "net"
"net/http"
+ "os"
+ "time"
"git.curoverse.com/arvados.git/sdk/go/arvados"
"git.curoverse.com/arvados.git/sdk/go/config"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to"
+ "golang.org/x/crypto/ssh"
check "gopkg.in/check.v1"
)
var live = flag.String("live-azure-cfg", "", "Test with real azure API, provide config file")
-func GetProvider() (Provider, ImageID, arvados.Cluster, error) {
+func GetProvider() (InstanceProvider, ImageID, arvados.Cluster, error) {
cluster := arvados.Cluster{
InstanceTypes: arvados.InstanceTypeMap(map[string]arvados.InstanceType{
"tiny": arvados.InstanceType{
if err != nil {
return nil, ImageID(""), cluster, err
}
- ap, err := NewAzureProvider(cfg, cluster, "test123")
+ ap, err := NewAzureProvider(cfg, "test123")
return ap, ImageID(cfg.Image), cluster, err
} else {
ap := AzureProvider{
azconfig: AzureProviderConfig{
BlobContainer: "vhds",
},
- arvconfig: cluster,
dispatcherID: "test123",
namePrefix: "compute-test123-",
}
c.Fatal("Error making provider", err)
}
+ f, err := os.Open("azconfig_sshkey.pub")
+ c.Assert(err, check.IsNil)
+
+ keybytes, err := ioutil.ReadAll(f)
+ c.Assert(err, check.IsNil)
+
+ pk, _, _, _, err := ssh.ParseAuthorizedKey(keybytes)
+ c.Assert(err, check.IsNil)
+
inst, err := ap.Create(context.Background(),
cluster.InstanceTypes["tiny"],
- img, map[string]string{"tag1": "bleep"})
+ img, map[string]string{"tag1": "bleep"},
+ pk)
c.Assert(err, check.IsNil)
log.Printf("Result %v %v", inst.String(), inst.Address())
+
}
func (*AzureProviderSuite) TestListInstances(c *check.C) {
c.Assert(err, check.IsNil)
for _, i := range l {
- tg, _ := i.GetTags(context.Background())
- log.Printf("%v %v %v %v", i.String(), i.Address(), i.InstanceType(), tg)
+ tg, _ := i.Tags(context.Background())
+ log.Printf("%v %v %v", i.String(), i.Address(), tg)
}
}
c.Assert(err, check.IsNil)
if len(l) > 0 {
- tg, _ := l[0].GetTags(context.Background())
+ tg, _ := l[0].Tags(context.Background())
log.Printf("tags are %v", tg)
}
}
+
+func (*AzureProviderSuite) TestSSH(c *check.C) {
+ ap, _, _, err := GetProvider()
+ if err != nil {
+ c.Fatal("Error making provider", err)
+ }
+ l, err := ap.Instances(context.Background())
+ c.Assert(err, check.IsNil)
+
+ if len(l) > 0 {
+
+ sshclient, err := SetupSSHClient(c, l[0].Address()+":2222")
+ c.Assert(err, check.IsNil)
+
+ sess, err := sshclient.NewSession()
+ c.Assert(err, check.IsNil)
+
+ out, err := sess.Output("ls /")
+ c.Assert(err, check.IsNil)
+
+ log.Printf("%v", out)
+
+ sshclient.Conn.Close()
+ }
+}
+
+func SetupSSHClient(c *check.C, addr string) (*ssh.Client, error) {
+ if addr == "" {
+ return nil, errors.New("instance has no address")
+ }
+
+ f, err := os.Open("azconfig_sshkey")
+ c.Assert(err, check.IsNil)
+
+ keybytes, err := ioutil.ReadAll(f)
+ c.Assert(err, check.IsNil)
+
+ priv, err := ssh.ParsePrivateKey(keybytes)
+ c.Assert(err, check.IsNil)
+
+ var receivedKey ssh.PublicKey
+ client, err := ssh.Dial("tcp", addr, &ssh.ClientConfig{
+ User: "crunch",
+ Auth: []ssh.AuthMethod{
+ ssh.PublicKeys(priv),
+ },
+ HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
+ receivedKey = key
+ return nil
+ },
+ Timeout: time.Minute,
+ })
+
+ if err != nil {
+ return nil, err
+ } else if receivedKey == nil {
+ return nil, errors.New("BUG: key was never provided to HostKeyCallback")
+ }
+
+ /*if wkr.publicKey == nil || !bytes.Equal(wkr.publicKey.Marshal(), receivedKey.Marshal()) {
+ err = wkr.instance.VerifyPublicKey(receivedKey, client)
+ if err != nil {
+ return nil, err
+ }
+ wkr.publicKey = receivedKey
+ }*/
+ return client, nil
+}
"time"
"git.curoverse.com/arvados.git/sdk/go/arvados"
+ "golang.org/x/crypto/ssh"
)
// A RateLimitError should be returned by a Provider when the cloud
// instance is implemented by the provider-specific instance types.
type Instance interface {
+ // ID returns the provider's instance ID. It must be stable
+ // for the life of the instance.
+ ID() InstanceID
+
// String typically returns the cloud-provided instance ID.
String() string
- // Configured Arvados instance type
- InstanceType() arvados.InstanceType
+
// Get tags
- GetTags(context.Context) (InstanceTags, error)
+ Tags(context.Context) (InstanceTags, error)
+
// Replace tags with the given tags
SetTags(context.Context, InstanceTags) error
+
// Shut down the node
Destroy(context.Context) error
+
// SSH server hostname or IP address, or empty string if unknown pending creation.
Address() string
}
-type Provider interface {
- Create(context.Context, arvados.InstanceType, ImageID, InstanceTags) (Instance, error)
+type InstanceProvider interface {
+ // Create a new instance. If supported by the driver, add the
+ // provided public key to /root/.ssh/authorized_keys.
+ //
+ // The returned error should implement RateLimitError and
+ // QuotaError where applicable.
+ Create(context.Context, arvados.InstanceType, ImageID, InstanceTags, ssh.PublicKey) (Instance, error)
+
+ // Return all instances, including ones that are booting or
+ // shutting down.
+ //
+ // An instance returned by successive calls to Instances() may
+ // -- but does not need to -- be represented by the same
+ // Instance object each time. Thus, the caller is responsible
+ // for de-duplicating the returned instances by comparing the
+ // InstanceIDs returned by the instances' ID() methods.
Instances(context.Context) ([]Instance, error)
+
+ // Stop any background tasks and release other resources.
+ Stop()
}