package weles
+import (
+ "crypto/rsa"
+ "net"
+)
+
// DryadJobStatus is a representation of current state of DryadJob.
type DryadJobStatus string
// Dryad contains information about device allocated for Job
// and credentials required to use it.
-type Dryad struct{}
+type Dryad struct {
+ // Address to Dryad.
+ Address net.Addr
+ // Username used to initialize ssh connection to Dryad
+ Username string
+ // Key is a private RSA key
+ Key rsa.PrivateKey
+}
// DryadJobFilter is used by List to access only jobs of interest.
//
// command may be terminated if the stdout and stderr is too large, err will be set.
//
// Large outputs should be redirected to files.
+ // FIXME: Exec function checks every argument and if contains space (except surrounding ones) surrounds the argument
+ // with double quotes. Caller must be aware of such functionality because it may brake some special arguments.
Exec(cmd []string, timeout time.Duration) (stdout, stderr []byte, err error)
// Close terminates session to Device.
package dryad
+// FIXME: When the connection is broken after it is established, all client functions stalls. This provider has to be rewritten.
+
import (
+ "bytes"
+ "time"
+
+ "strings"
+
+ "crypto/rsa"
+
. "git.tizen.org/tools/weles"
+ "golang.org/x/crypto/ssh"
)
-// NewSessionProvider is a stub.
-func NewSessionProvider(dryad Dryad) SessionProvider {
- return nil
+type sshClient struct {
+ config *ssh.ClientConfig
+ client *ssh.Client
+}
+
+// sessionProvider implements SessionProvider interface.
+type sessionProvider struct {
+ SessionProvider
+ dryad Dryad
+ connection *sshClient
+}
+
+func prepareSSHConfig(userName string, key rsa.PrivateKey) (*ssh.ClientConfig, error) {
+ signer, err := ssh.NewSignerFromKey(&key)
+ if err != nil {
+ return nil, err
+ }
+
+ return &ssh.ClientConfig{
+ User: userName,
+ Auth: []ssh.AuthMethod{
+ ssh.PublicKeys(signer),
+ },
+ HostKeyCallback: ssh.InsecureIgnoreHostKey(),
+ Timeout: 30 * time.Second, // TODO: Use value from config when such appears.
+ }, nil
+}
+
+func (d *sessionProvider) connect() (err error) {
+ d.connection.client, err = ssh.Dial("tcp", d.dryad.Address.String(), d.connection.config)
+ return
+}
+
+func (d *sessionProvider) newSession() (*ssh.Session, error) {
+ if d.connection.client == nil {
+ err := d.connect()
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ session, err := d.connection.client.NewSession()
+ if err != nil {
+ return nil, err
+ }
+
+ return session, nil
+}
+
+func (d *sessionProvider) executeRemoteCommand(cmd string) ([]byte, []byte, error) {
+ session, err := d.newSession()
+ if err != nil {
+ return nil, nil, err
+ }
+ defer session.Close()
+
+ var stdout, stderr bytes.Buffer
+ session.Stdout = &stdout
+ session.Stderr = &stderr
+
+ err = session.Run(cmd)
+ return stdout.Bytes(), stderr.Bytes(), err
+}
+
+// NewSessionProvider returns new instance of SessionProvider or nil if error occurs.
+func NewSessionProvider(dryad Dryad) (SessionProvider, error) {
+ cfg, err := prepareSSHConfig(dryad.Username, dryad.Key)
+ if err != nil {
+ return nil, err
+ }
+
+ return &sessionProvider{
+ dryad: dryad,
+ connection: &sshClient{
+ config: cfg,
+ client: nil,
+ },
+ }, nil
+}
+
+// Exec is a part of SessionProvider interface.
+func (d *sessionProvider) Exec(cmd []string) (stdout, stderr []byte, err error) {
+ joinedCommand := cmd[0] + " "
+ for i := 1; i < len(cmd); i++ {
+ if strings.Contains(strings.Trim(cmd[i], " "), " ") {
+ joinedCommand += `"` + cmd[i] + `" `
+ } else {
+ joinedCommand += cmd[i] + " "
+ }
+ }
+ return d.executeRemoteCommand(joinedCommand)
+}
+
+// DUT is a part of SessionProvider interface.
+func (d *sessionProvider) DUT() error {
+ _, _, err := d.executeRemoteCommand("stm -dut")
+ return err
+}
+
+// TS is a part of SessionProvider interface.
+func (d *sessionProvider) TS() error {
+ _, _, err := d.executeRemoteCommand("stm -ts")
+ return err
+}
+
+// Close is a part of SessionProvider interface.
+func (d *sessionProvider) PowerTick() error {
+ _, _, err := d.executeRemoteCommand("stm -tick")
+ return err
+}
+
+// Close is a part of SessionProvider interface.
+func (d *sessionProvider) Close() error {
+ if d.connection.client == nil {
+ return nil
+ }
+
+ err := d.connection.client.Close()
+ d.connection.client = nil
+ return err
}
--- /dev/null
+/*
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package dryad_test
+
+import (
+ "strings"
+
+ "git.tizen.org/tools/weles/manager/dryad"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("SessionProvider", func() {
+ var sp dryad.SessionProvider
+
+ BeforeEach(func() {
+ if !accessInfoGiven {
+ Skip("No valid access info to Dryad")
+ }
+ sp, _ = dryad.NewSessionProvider(dryadInfo)
+ })
+
+ AfterEach(func() {
+ sp.Close()
+ })
+
+ It("should write poem to a file", func() {
+ stdout, stderr, err := sp.Exec([]string{"echo", flyingCows, " > ", flyingCowsPath})
+ Expect(err).ToNot(HaveOccurred())
+ Expect(stdout).To(BeEmpty())
+ Expect(stderr).To(BeEmpty())
+ })
+
+ It("should read poem from a file", func() {
+ stdout, stderr, err := sp.Exec([]string{"cat", flyingCowsPath})
+ Expect(err).ToNot(HaveOccurred())
+ Expect(strings.TrimRight(string(stdout), "\n")).To(BeIdenticalTo(flyingCows))
+ Expect(stderr).To(BeEmpty())
+ })
+
+ It("should not read poem from nonexistent file", func() {
+ stdout, stderr, err := sp.Exec([]string{"cat", "/Ihopethispathdoesnotexit/" + flyingCowsPath + ".txt"})
+ Expect(err).To(HaveOccurred())
+ Expect(stdout).To(BeEmpty())
+ Expect(stderr).ToNot(BeEmpty())
+ })
+
+ It("should switch to DUT", func() {
+ Expect(sp.DUT()).ToNot(HaveOccurred())
+ })
+
+ It("should tick DUT's power supply", func() {
+ Expect(sp.PowerTick()).ToNot(HaveOccurred())
+ })
+
+ It("should switch to TS", func() {
+ Expect(sp.TS()).ToNot(HaveOccurred())
+ })
+})
--- /dev/null
+/*
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package dryad_test
+
+import (
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+
+ "flag"
+ "io/ioutil"
+ "net"
+ "testing"
+
+ "crypto/x509"
+
+ "encoding/pem"
+
+ . "git.tizen.org/tools/weles"
+)
+
+func TestManager(t *testing.T) {
+ RegisterFailHandler(Fail)
+ RunSpecs(t, "Dryad Suite")
+}
+
+var (
+ dryadAddress string
+ userName string
+ keyFile string
+
+ flyingCows = `Cows called Daisy
+Are often lazy.
+But cows called Brian
+They be flyin'
+Up in the air
+And out into space
+Because of the grass
+And the gasses it makes!`
+
+ flyingCowsPath = "/tmp/flyingCow.txt"
+
+ dryadInfo Dryad
+ accessInfoGiven bool
+)
+
+func init() {
+ flag.StringVar(&dryadAddress, "address", "", "IP address to dryad")
+ flag.StringVar(&userName, "userName", "", "user name")
+ flag.StringVar(&keyFile, "keyFile", "", "path to file containing private part of ssh key")
+}
+
+var _ = BeforeSuite(func() {
+ if dryadAddress != "" && userName != "" && keyFile != "" {
+ accessInfoGiven = true
+ strkey, err := ioutil.ReadFile(keyFile)
+ if err != nil {
+ Skip("Error reading key file: " + err.Error())
+ }
+
+ block, _ := pem.Decode(strkey)
+ if block == nil {
+ Skip("Error reading key file: " + err.Error())
+ }
+
+ key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
+ if err != nil {
+ Skip("Error parsing key file: " + err.Error())
+ }
+
+ dryadInfo = Dryad{
+ Address: &net.TCPAddr{net.ParseIP(dryadAddress), 22, ""},
+ Username: userName,
+ Key: *key,
+ }
+ }
+})
--- /dev/null
+/*
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+// File manager/dryad/error.go contains definitions of errors.
+
+package dryad
+
+import "errors"
+
+var (
+ // ErrConnectionClosed is returned when caller tries to close already closed connection to Dryad.
+ ErrConnectionClosed = errors.New("attempt to close already closed connection")
+)
// newDryadJob creates an instance of dryadJob and starts a goroutine
// executing phases of given job implemented by provider of DryadJobRunner interface.
func newDryadJob(job JobID, rusalka Dryad, changes chan<- DryadJobStatusChange) *dryadJob {
- session := dryad.NewSessionProvider(rusalka)
+ session, _ := dryad.NewSessionProvider(rusalka) // FIXME: Make use of returned error.
device := dryad.NewDeviceCommunicationProvider(session)
ctx, cancel := context.WithCancel(context.Background())