From 7b126a9c23d106708f2b9ee5cb183b118f5f5990 Mon Sep 17 00:00:00 2001 From: Maciej Wereski Date: Fri, 10 Nov 2017 15:12:31 +0100 Subject: [PATCH] HTTP API Client: Acquire worker Definition of boruta/http.AccessInfo2.Addr had to be changed to specific type (new.TCPAddr) rather than interface, as it's not possible to marshal interface type to JSON. Change-Id: I37afd203db356ba4621ad7448a554b59f6553ce9 Signed-off-by: Maciej Wereski --- http/client/client.go | 28 ++++++++++-- http/client/client_test.go | 96 ++++++++++++++++++++++++++++++++++++++---- http/http.go | 2 +- http/server/api/v1/handlers.go | 3 +- 4 files changed, 116 insertions(+), 13 deletions(-) diff --git a/http/client/client.go b/http/client/client.go index 0de4078..26b16dd 100644 --- a/http/client/client.go +++ b/http/client/client.go @@ -23,7 +23,9 @@ package client import ( "bytes" + "crypto/x509" "encoding/json" + "encoding/pem" "errors" "io" "io/ioutil" @@ -237,9 +239,29 @@ func (client *BorutaClient) ListRequests(filter boruta.ListFilter) ([]boruta.Req // AcquireWorker queries Boruta server for information required to access // assigned Dryad. Access information may not be available when the call // is issued because requests need to have assigned worker. -func (client *BorutaClient) AcquireWorker(reqID boruta.ReqID) ( - *boruta.AccessInfo, error) { - return nil, util.ErrNotImplemented +func (client *BorutaClient) AcquireWorker(reqID boruta.ReqID) (boruta.AccessInfo, error) { + var accInfo boruta.AccessInfo + path := client.url + "reqs/" + strconv.Itoa(int(reqID)) + "/acquire_worker" + resp, err := http.Post(path, "", nil) + if err != nil { + return accInfo, err + } + accInfo2 := new(util.AccessInfo2) + if err = processResponse(resp, &accInfo2); err != nil { + return accInfo, err + } + block, _ := pem.Decode([]byte(accInfo2.Key)) + if block == nil || block.Type != "RSA PRIVATE KEY" { + return accInfo, errors.New("wrong key: " + accInfo2.Key) + } + key, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return accInfo, err + } + accInfo.Addr = accInfo2.Addr + accInfo.Username = accInfo2.Username + accInfo.Key = *key + return accInfo, nil } // ProlongAccess requests Boruta server to extend running time of job. User may diff --git a/http/client/client_test.go b/http/client/client_test.go index 2eda6df..94b4ca1 100644 --- a/http/client/client_test.go +++ b/http/client/client_test.go @@ -17,9 +17,12 @@ package client import ( + "crypto/x509" "encoding/json" + "encoding/pem" "errors" "io/ioutil" + "net" "net/http" "net/http/httptest" "strings" @@ -43,6 +46,8 @@ type testCase struct { contentType string // expected status status int + // response that should be returned (read from server testdata if empty) + resp string // expected headers header http.Header } @@ -178,9 +183,14 @@ func prepareServer(method string, tests []*testCase) *httptest.Server { default: fpath += ".txt" } - data, err = ioutil.ReadFile(fpath) - if err != nil { - panic(err) + // Or use reply provided in testcase instead. + if test.resp == "" { + data, err = ioutil.ReadFile(fpath) + if err != nil { + panic(err) + } + } else { + data = []byte(test.resp) } w.Header().Set("Content-Type", test.contentType) } @@ -602,12 +612,82 @@ func TestListRequests(t *testing.T) { } func TestAcquireWorker(t *testing.T) { - assert, client := initTest(t, "") - assert.NotNil(client) + prefix := "acquire-worker-" + path := "/api/v1/reqs/" - accessInfo, err := client.AcquireWorker(ReqID(0)) - assert.Nil(accessInfo) - assert.Equal(util.ErrNotImplemented, err) + badkeyAI := util.AccessInfo2{ + Addr: &net.TCPAddr{ + IP: net.IPv4(127, 0, 0, 1), + Port: 22, + }, + Key: "bad key :(", + Username: "lelpolel", + } + + tests := []*testCase{ + &testCase{ + // valid request + name: prefix + "valid", + path: path + "1/acquire_worker", + contentType: contentJSON, + status: http.StatusOK, + }, + &testCase{ + // missing request + name: prefix + "missing", + path: path + "2/acquire_worker", + contentType: contentJSON, + status: http.StatusNotFound, + }, + &testCase{ + // bad key request + name: prefix + "badkey", + path: path + "3/acquire_worker", + contentType: contentJSON, + status: http.StatusOK, + resp: string(jsonMustMarshal(badkeyAI)), + }, + } + + srv := prepareServer(http.MethodPost, tests) + defer srv.Close() + assert, client := initTest(t, srv.URL) + + // from server/api/v1 AcquireWorker tests + keyPem := "-----BEGIN RSA PRIVATE KEY-----\nMIICXgIBAAKBgQCyBgKbrwKh75BDoigwltbazFGDLdlxf9YLpFj5v+4ieKgsaN+W\n+kRvamSuB5CC2tqFql5x7kPt1U+vVMwkzVRewF/HHzRYxgLHlge6d1ZALpCWywaz\nslt5pNCmF7NoZ//WTSrafufDI4IRoNgkHtEKvnWdBaPPnY4Cf+PCbZOYNQIDAQAB\nAoGBAJvoz5fxKekQmdPhzDjhocF1d13fZbQVNSx0/seb476k1QQvxMHA5PZ+wzX2\nwgUYDpFJp/U3qp48VtFC/pasjNoG7zLPLLUJcg15eOoh4Ld7I1e4lRkLl3CwnqMk\nbc6UoKQRLli4O3cmaMxVHXal0o72s3o0qnHlRlZXLekwi6aBAkEA69j3bnbAybsF\n/NHelRYDH8bou+LCX2d/p6ReUR0bJ4yCAWRi/9Ld0ng482xinxGSpfovbIplBMFx\nH2eT2Cw0OQJBAME8LLz/3zb/vLG/t8Lfsequ1ZhVca/LlVR4yJLlyaVcywT9SJlO\nmKCy13SpKl8TY7czyufYrY4lobZjYaIsm90CQQCKhkRGWG/BzRymMyp2DJjHKFB4\nUqbx3FuJPqy7HcpeP1P4t1rCgbsSLNTefRGr9mlZHYqPSPYuheQImxCmTshZAkEA\nwAp5u+vfft1yPoT2r+l4/G99P8PLFJcTdbwEOlm8qWcrLW47dIE0FqEml3536b1v\nYGdMxFYHRjoIGSdzpKUI0QJAAqPdDp+y7kWaeIbKkp3Z3bLrj5wii2QAy2YlBDKe\npXrvruWJvL75OCYcxRju3DpVaoYqEmso+UEiQEDRB42YYg==\n-----END RSA PRIVATE KEY-----\n" + block, _ := pem.Decode([]byte(keyPem)) + assert.NotNil(block) + key, _ := x509.ParsePKCS1PrivateKey(block.Bytes) + + access := AccessInfo{ + Addr: &net.TCPAddr{ + IP: net.IPv4(127, 0, 0, 1), + Port: 22, + }, + Key: *key, + Username: "wołchw", + } + + // valid + accessInfo, err := client.AcquireWorker(ReqID(1)) + assert.Equal(access, accessInfo) + assert.Nil(err) + + // missing + accessInfo, err = client.AcquireWorker(ReqID(2)) + assert.Zero(accessInfo) + assert.Equal(errReqNotFound, err) + + // bad key + accessInfo, err = client.AcquireWorker(ReqID(3)) + assert.Zero(accessInfo) + assert.Equal(errors.New("wrong key: "+badkeyAI.Key), err) + + // http.Post failure + client.url = "http://nosuchaddress.fail" + accessInfo, err = client.AcquireWorker(ReqID(1)) + assert.Zero(accessInfo) + assert.NotNil(err) } func TestProlongAccess(t *testing.T) { diff --git a/http/http.go b/http/http.go index 4f137bb..a5d8973 100644 --- a/http/http.go +++ b/http/http.go @@ -39,7 +39,7 @@ type WorkerStatePack struct { // replaces with users' public keys when proper user support is added. type AccessInfo2 struct { // Addr is necessary information to connect to a tunnel to Dryad. - Addr net.Addr + Addr *net.TCPAddr // Key is private RSA key in PEM format. Key string // Username is a login name for the job session. diff --git a/http/server/api/v1/handlers.go b/http/server/api/v1/handlers.go index c534ca8..f1295e5 100644 --- a/http/server/api/v1/handlers.go +++ b/http/server/api/v1/handlers.go @@ -23,6 +23,7 @@ import ( "encoding/json" "encoding/pem" "io" + "net" "net/http" . "git.tizen.org/tools/boruta" @@ -149,7 +150,7 @@ func (api *API) acquireWorkerHandler(r *http.Request, ps map[string]string) resp Bytes: x509.MarshalPKCS1PrivateKey(&accessInfo.Key), })) return util.AccessInfo2{ - Addr: accessInfo.Addr, + Addr: accessInfo.Addr.(*net.TCPAddr), Key: key, Username: accessInfo.Username, } -- 2.7.4