c23360b32c24de37791bf4f273488dd2563b5591
[arvados.git] / lib / dispatchcloud / azure_test.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package dispatchcloud
6
7 import (
8         "context"
9         "errors"
10         "flag"
11         "io/ioutil"
12         "log"
13         "net"
14         "net/http"
15         "os"
16         "time"
17
18         "git.curoverse.com/arvados.git/sdk/go/arvados"
19         "git.curoverse.com/arvados.git/sdk/go/config"
20         "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-06-01/compute"
21         "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-06-01/network"
22         "github.com/Azure/go-autorest/autorest"
23         "github.com/Azure/go-autorest/autorest/azure"
24         "github.com/Azure/go-autorest/autorest/to"
25         "golang.org/x/crypto/ssh"
26         check "gopkg.in/check.v1"
27 )
28
29 type AzureProviderSuite struct{}
30
31 var _ = check.Suite(&AzureProviderSuite{})
32
33 type VirtualMachinesClientStub struct{}
34
35 func (*VirtualMachinesClientStub) CreateOrUpdate(ctx context.Context,
36         resourceGroupName string,
37         VMName string,
38         parameters compute.VirtualMachine) (result compute.VirtualMachine, err error) {
39         parameters.ID = &VMName
40         parameters.Name = &VMName
41         return parameters, nil
42 }
43
44 func (*VirtualMachinesClientStub) Delete(ctx context.Context, resourceGroupName string, VMName string) (result *http.Response, err error) {
45         return nil, nil
46 }
47
48 func (*VirtualMachinesClientStub) ListComplete(ctx context.Context, resourceGroupName string) (result compute.VirtualMachineListResultIterator, err error) {
49         return compute.VirtualMachineListResultIterator{}, nil
50 }
51
52 type InterfacesClientStub struct{}
53
54 func (*InterfacesClientStub) CreateOrUpdate(ctx context.Context,
55         resourceGroupName string,
56         nicName string,
57         parameters network.Interface) (result network.Interface, err error) {
58         parameters.ID = to.StringPtr(nicName)
59         (*parameters.IPConfigurations)[0].PrivateIPAddress = to.StringPtr("192.168.5.5")
60         return parameters, nil
61 }
62
63 func (*InterfacesClientStub) Delete(ctx context.Context, resourceGroupName string, VMName string) (result *http.Response, err error) {
64         return nil, nil
65 }
66
67 func (*InterfacesClientStub) ListComplete(ctx context.Context, resourceGroupName string) (result network.InterfaceListResultIterator, err error) {
68         return network.InterfaceListResultIterator{}, nil
69 }
70
71 var live = flag.String("live-azure-cfg", "", "Test with real azure API, provide config file")
72
73 func GetProvider() (InstanceProvider, ImageID, arvados.Cluster, error) {
74         cluster := arvados.Cluster{
75                 InstanceTypes: arvados.InstanceTypeMap(map[string]arvados.InstanceType{
76                         "tiny": arvados.InstanceType{
77                                 Name:         "tiny",
78                                 ProviderType: "Standard_D1_v2",
79                                 VCPUs:        1,
80                                 RAM:          4000000000,
81                                 Scratch:      10000000000,
82                                 Price:        .02,
83                                 Preemptible:  false,
84                         },
85                 })}
86         if *live != "" {
87                 cfg := AzureProviderConfig{}
88                 err := config.LoadFile(&cfg, *live)
89                 if err != nil {
90                         return nil, ImageID(""), cluster, err
91                 }
92                 ap, err := NewAzureProvider(cfg, "test123")
93                 return ap, ImageID(cfg.Image), cluster, err
94         } else {
95                 ap := AzureProvider{
96                         azconfig: AzureProviderConfig{
97                                 BlobContainer: "vhds",
98                         },
99                         dispatcherID: "test123",
100                         namePrefix:   "compute-test123-",
101                 }
102                 ap.vmClient = &VirtualMachinesClientStub{}
103                 ap.netClient = &InterfacesClientStub{}
104                 return &ap, ImageID("blob"), cluster, nil
105         }
106 }
107
108 func (*AzureProviderSuite) TestCreate(c *check.C) {
109         ap, img, cluster, err := GetProvider()
110         if err != nil {
111                 c.Fatal("Error making provider", err)
112         }
113
114         f, err := os.Open("azconfig_sshkey.pub")
115         c.Assert(err, check.IsNil)
116
117         keybytes, err := ioutil.ReadAll(f)
118         c.Assert(err, check.IsNil)
119
120         pk, _, _, _, err := ssh.ParseAuthorizedKey(keybytes)
121         c.Assert(err, check.IsNil)
122
123         inst, err := ap.Create(context.Background(),
124                 cluster.InstanceTypes["tiny"],
125                 img, map[string]string{"tag1": "bleep"},
126                 pk)
127
128         c.Assert(err, check.IsNil)
129
130         log.Printf("Result %v %v", inst.String(), inst.Address())
131
132 }
133
134 func (*AzureProviderSuite) TestListInstances(c *check.C) {
135         ap, _, _, err := GetProvider()
136         if err != nil {
137                 c.Fatal("Error making provider", err)
138         }
139
140         l, err := ap.Instances(context.Background())
141
142         c.Assert(err, check.IsNil)
143
144         for _, i := range l {
145                 tg, _ := i.Tags(context.Background())
146                 log.Printf("%v %v %v", i.String(), i.Address(), tg)
147         }
148 }
149
150 func (*AzureProviderSuite) TestManageNics(c *check.C) {
151         ap, _, _, err := GetProvider()
152         if err != nil {
153                 c.Fatal("Error making provider", err)
154         }
155
156         ap.(*AzureProvider).ManageNics(context.Background())
157 }
158
159 func (*AzureProviderSuite) TestManageBlobs(c *check.C) {
160         ap, _, _, err := GetProvider()
161         if err != nil {
162                 c.Fatal("Error making provider", err)
163         }
164
165         ap.(*AzureProvider).ManageBlobs(context.Background())
166 }
167
168 func (*AzureProviderSuite) TestDestroyInstances(c *check.C) {
169         ap, _, _, err := GetProvider()
170         if err != nil {
171                 c.Fatal("Error making provider", err)
172         }
173
174         l, err := ap.Instances(context.Background())
175         c.Assert(err, check.IsNil)
176
177         for _, i := range l {
178                 c.Check(i.Destroy(context.Background()), check.IsNil)
179         }
180 }
181
182 func (*AzureProviderSuite) TestDeleteFake(c *check.C) {
183         ap, _, _, err := GetProvider()
184         if err != nil {
185                 c.Fatal("Error making provider", err)
186         }
187
188         _, err = ap.(*AzureProvider).netClient.Delete(context.Background(), "fakefakefake", "fakefakefake")
189
190         de, ok := err.(autorest.DetailedError)
191         if ok {
192                 rq := de.Original.(*azure.RequestError)
193
194                 log.Printf("%v %q %q", rq.Response.StatusCode, rq.ServiceError.Code, rq.ServiceError.Message)
195         }
196 }
197
198 func (*AzureProviderSuite) TestWrapError(c *check.C) {
199         retryError := autorest.DetailedError{
200                 Original: &azure.RequestError{
201                         DetailedError: autorest.DetailedError{
202                                 Response: &http.Response{
203                                         StatusCode: 429,
204                                         Header:     map[string][]string{"Retry-After": []string{"123"}},
205                                 },
206                         },
207                         ServiceError: &azure.ServiceError{},
208                 },
209         }
210         wrapped := WrapAzureError(retryError)
211         _, ok := wrapped.(RateLimitError)
212         c.Check(ok, check.Equals, true)
213
214         quotaError := autorest.DetailedError{
215                 Original: &azure.RequestError{
216                         DetailedError: autorest.DetailedError{
217                                 Response: &http.Response{
218                                         StatusCode: 503,
219                                 },
220                         },
221                         ServiceError: &azure.ServiceError{
222                                 Message: "No more quota",
223                         },
224                 },
225         }
226         wrapped = WrapAzureError(quotaError)
227         _, ok = wrapped.(QuotaError)
228         c.Check(ok, check.Equals, true)
229 }
230
231 func (*AzureProviderSuite) TestSetTags(c *check.C) {
232         ap, _, _, err := GetProvider()
233         if err != nil {
234                 c.Fatal("Error making provider", err)
235         }
236         l, err := ap.Instances(context.Background())
237         c.Assert(err, check.IsNil)
238
239         if len(l) > 0 {
240                 err = l[0].SetTags(context.Background(), map[string]string{"foo": "bar"})
241                 if err != nil {
242                         c.Fatal("Error setting tags", err)
243                 }
244         }
245         l, err = ap.Instances(context.Background())
246         c.Assert(err, check.IsNil)
247
248         if len(l) > 0 {
249                 tg, _ := l[0].Tags(context.Background())
250                 log.Printf("tags are %v", tg)
251         }
252 }
253
254 func (*AzureProviderSuite) TestSSH(c *check.C) {
255         ap, _, _, err := GetProvider()
256         if err != nil {
257                 c.Fatal("Error making provider", err)
258         }
259         l, err := ap.Instances(context.Background())
260         c.Assert(err, check.IsNil)
261
262         if len(l) > 0 {
263
264                 sshclient, err := SetupSSHClient(c, l[0].Address()+":2222")
265                 c.Assert(err, check.IsNil)
266
267                 sess, err := sshclient.NewSession()
268                 c.Assert(err, check.IsNil)
269
270                 out, err := sess.Output("ls /")
271                 c.Assert(err, check.IsNil)
272
273                 log.Printf("%v", out)
274
275                 sshclient.Conn.Close()
276         }
277 }
278
279 func SetupSSHClient(c *check.C, addr string) (*ssh.Client, error) {
280         if addr == "" {
281                 return nil, errors.New("instance has no address")
282         }
283
284         f, err := os.Open("azconfig_sshkey")
285         c.Assert(err, check.IsNil)
286
287         keybytes, err := ioutil.ReadAll(f)
288         c.Assert(err, check.IsNil)
289
290         priv, err := ssh.ParsePrivateKey(keybytes)
291         c.Assert(err, check.IsNil)
292
293         var receivedKey ssh.PublicKey
294         client, err := ssh.Dial("tcp", addr, &ssh.ClientConfig{
295                 User: "crunch",
296                 Auth: []ssh.AuthMethod{
297                         ssh.PublicKeys(priv),
298                 },
299                 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
300                         receivedKey = key
301                         return nil
302                 },
303                 Timeout: time.Minute,
304         })
305
306         if err != nil {
307                 return nil, err
308         } else if receivedKey == nil {
309                 return nil, errors.New("BUG: key was never provided to HostKeyCallback")
310         }
311
312         /*if wkr.publicKey == nil || !bytes.Equal(wkr.publicKey.Marshal(), receivedKey.Marshal()) {
313                 err = wkr.instance.VerifyPublicKey(receivedKey, client)
314                 if err != nil {
315                         return nil, err
316                 }
317                 wkr.publicKey = receivedKey
318         }*/
319         return client, nil
320 }