package dryad
import (
+ "bytes"
+ "time"
+
+ "strings"
+
+ "crypto/rsa"
+
+ "fmt"
+
. "git.tizen.org/tools/weles"
+ "golang.org/x/crypto/ssh"
+)
+
+const (
+ stmCommand = "/usr/local/stm"
)
-// NewSessionProvider is a stub.
+type sshClient struct {
+ config *ssh.ClientConfig
+ client *ssh.Client
+}
+
+// sessionProvider implements SessionProvider interface.
+// FIXME: When the connection is broken after it is established, all client functions stall. This provider has to be rewritten.
+type sessionProvider struct {
+ SessionProvider
+ dryad Dryad
+ connection *sshClient
+}
+
+func prepareSSHConfig(userName string, key rsa.PrivateKey) *ssh.ClientConfig {
+ signer, _ := ssh.NewSignerFromKey(&key)
+
+ 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.
+ }
+}
+
+func (d *sessionProvider) connect() (err error) {
+ d.connection.client, err = ssh.Dial("tcp", d.dryad.Addr.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.
func NewSessionProvider(dryad Dryad) SessionProvider {
+ cfg := prepareSSHConfig(dryad.Username, dryad.Key)
+
+ return &sessionProvider{
+ dryad: dryad,
+ connection: &sshClient{
+ config: cfg,
+ },
+ }
+}
+
+// Exec is a part of SessionProvider interface.
+// 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 break some special arguments.
+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.
+// This function requires 'stm' binary on MuxPi's NanoPi.
+func (d *sessionProvider) DUT() error {
+ _, stderr, err := d.executeRemoteCommand(stmCommand + " -dut")
+ if err != nil {
+ return fmt.Errorf("DUT command failed: %s : %s", err, stderr)
+ }
return nil
}
+
+// TS is a part of SessionProvider interface.
+// This function requires 'stm' binary on MuxPi's NanoPi.
+func (d *sessionProvider) TS() error {
+ _, stderr, err := d.executeRemoteCommand(stmCommand + " -ts")
+ if err != nil {
+ return fmt.Errorf("TS command failed: %s : %s", err, stderr)
+ }
+ return nil
+}
+
+// PowerTick is a part of SessionProvider interface.
+// This function requires 'stm' binary on MuxPi's NanoPi.
+func (d *sessionProvider) PowerTick() error {
+ _, stderr, err := d.executeRemoteCommand(stmCommand + " -tick")
+ if err != nil {
+ return fmt.Errorf("PowerTick command failed: %s : %s", err, stderr)
+ }
+ return nil
+}
+
+// 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
+
+import (
+ "strings"
+
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("SessionProvider", func() {
+ var sp SessionProvider
+
+ BeforeEach(func() {
+ if !accessInfoGiven {
+ Skip("No valid access info to Dryad")
+ }
+ sp = 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", "/Ihopethispathdoesnotexist/" + 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
+
+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
+ port int
+ 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.IntVar(&port, "port", 22, "SSH port to use for connection")
+ 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 decoding PEM block from key file contents")
+ }
+
+ key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
+ if err != nil {
+ Skip("Error parsing key file: " + err.Error())
+ }
+
+ dryadInfo = Dryad{
+ Addr: &net.TCPAddr{net.ParseIP(dryadAddress), port, ""},
+ 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")
+)