Merge lp:~canonical-platform-qa/snappy-ecosystem-tests/helpers_for_snapd_cli into lp:snappy-ecosystem-tests
- helpers_for_snapd_cli
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Heber Parrucci |
Approved revision: | 37 |
Merged at revision: | 23 |
Proposed branch: | lp:~canonical-platform-qa/snappy-ecosystem-tests/helpers_for_snapd_cli |
Merge into: | lp:snappy-ecosystem-tests |
Prerequisite: | lp:~canonical-platform-qa/snappy-ecosystem-tests/minor_tweaks |
Diff against target: |
359 lines (+314/-0) 6 files modified
README.rst (+6/-0) requirements.txt (+2/-0) snappy_ecosystem_tests/helpers/snapd/snapd.py (+137/-0) snappy_ecosystem_tests/tests/test_snapd.py (+42/-0) snappy_ecosystem_tests/utils/ssh.py (+92/-0) snappy_ecosystem_tests/utils/user.py (+35/-0) |
To merge this branch: | bzr merge lp:~canonical-platform-qa/snappy-ecosystem-tests/helpers_for_snapd_cli |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Heber Parrucci (community) | Approve | ||
Santiago Baldassin (community) | Approve | ||
platform-qa-bot | continuous-integration | Approve | |
I Ahmad (community) | Needs Fixing | ||
Review via email: mp+316459@code.launchpad.net |
Commit message
Basic helpers around snapd' command line interface
Description of the change
Basic helpers around snapd' command line interface
I Ahmad (iahmad) : | # |
- 8. By Omer Akram
-
merge with trunk
- 9. By Omer Akram
-
Assert when logged in
Omer Akram (om26er) wrote : | # |
Replied inline.
- 10. By Omer Akram
-
fix login command
I Ahmad (iahmad) wrote : | # |
I am fine as long as data is not parsed/filtered within helper method
On Tue, Feb 7, 2017 at 7:48 PM, Omer Akram <email address hidden> wrote:
> Replied inline.
>
> Diff comments:
>
> >
> > === modified file 'tests/
> > --- tests/utils/
> > +++ tests/utils/
> > @@ -17,3 +17,138 @@
> > # You should have received a copy of the GNU General Public License
> > # along with this program. If not, see <http://
> > #
> > +
> > +import getpass
> > +import os
> > +import subprocess
> > +import sys
> > +import shlex
> > +import shutil
> > +import tempfile
> > +
> > +import pexpect
> > +import yaml
> > +
> > +PATH_SNAP = '/usr/bin/snap'
> > +COMMAND_DOWNLOAD = 'download {snap} --channel=
> > +COMMAND_FIND = 'find {search_term}'
> > +COMMAND_INFO = 'info {snap}'
> > +COMMAND_INSTALL = 'install {snap} --channel=
> > +COMMAND_LOGIN = 'login {email}'
> > +COMMAND_LOGOUT = 'logout'
> > +COMMAND_REFRESH = 'refresh {snap} --channel=
> > +CHANNEL_STABLE = 'stable'
> > +PASSWORD_ROOT = 'changed'
> > +
> > +
> > +def is_root_user():
> > + """Return bool representing if the current user is root."""
> > + return os.getuid() == 0
> > +
> > +
> > +def run_snapd_
> > + """ Run the request snapd cli command.
> > +
> > + :param parameters: the snapd sub-command and their parameters.
> > + example: install core --beta
> > + :param return_output: Whether to return the stdout of the command.
> > + :param cwd: The current working directory for the command, defaults
> to
> > + pwd.
> > + :return: Optionally return the stdout of the command, depending if
> > + the value of `return_output` was True.
> > + """
> > + command = '{} {}'.format(
> > + if return_output:
> > + raw_output = subprocess.
> > + shlex.split(
> > + return raw_output.
> > + subprocess.
> > +
> > +
> > +def login(email, password):
> > + """Login to snapd.
> > +
> > + :param email: Ubuntu SSO account email address.
> > + :param password: Ubuntu SSO account password.
> > + """
> > + command = '{} {}'.format(
> > + if is_root_user():
> > + p = pexpect.
> > + else:
> > + p = pexpect.spawnu('{} {}'.format('sudo', command),
> logfile=sys.stdout)
> > + p.expect_exact(
> > + '[sudo] password for {}: '.format(
> > + p.sendline(
> > + p.expect_
> > + p.sendline(
> > + p.wait()
> > +
>
> I added an assert to ensure the login was successful.
>
> > +
> > +def logout():
> > + run_snapd_
> > +
> > +
> > +def download(snap, channel=
> > + """Download the requested snap.
> > +
> > + :param snap: name of the snap to download.
> > + :param channel:...
- 11. By Omer Akram
-
Simplify .
./snapd.py
./snapcraft.py
./__pycache__
./__pycache__/snapd. cpython- 35.pyc
./__init__.py method to remove client side filtering
Omer Akram (om26er) wrote : | # |
Ok, removed the filtering code now.
platform-qa-bot (platform-qa-bot) wrote : | # |
FAILED: Continuous integration, rev:11
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
- 12. By Omer Akram
-
Merge with trunk
platform-qa-bot (platform-qa-bot) wrote : | # |
FAILED: Continuous integration, rev:12
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
Omer Akram (om26er) wrote : | # |
I have made the suggested changes.
- 13. By Omer Akram
-
Fix pylint complaints
platform-qa-bot (platform-qa-bot) wrote : | # |
PASSED: Continuous integration, rev:13
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
- 14. By Omer Akram
-
add fixme comment
platform-qa-bot (platform-qa-bot) wrote : | # |
PASSED: Continuous integration, rev:14
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
- 15. By Omer Akram
-
more compact code. always return command output
platform-qa-bot (platform-qa-bot) wrote : | # |
PASSED: Continuous integration, rev:15
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
Heber Parrucci (heber013) wrote : | # |
Looks good in general. Couple of comments inline. Additionally:
* Merge from trunk
* Can you add a single test that checks if it is possible to login in snapd? in /tests folder
* Can you move snapd.py from utils to helpers?
- 16. By Omer Akram
-
revert
- 17. By Omer Akram
-
merge with trunk
- 18. By Omer Akram
-
bring back changes
- 19. By Omer Akram
-
read password from config, other changes
- 20. By Omer Akram
-
add simple test case for snapd
Omer Akram (om26er) wrote : | # |
Replied inline also added a few simple test cases.
Heber Parrucci (heber013) wrote : | # |
Looks good. One comment inline
Omer Akram (om26er) wrote : | # |
sure, let me fix that.
platform-qa-bot (platform-qa-bot) wrote : | # |
FAILED: Continuous integration, rev:20
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
- 21. By Omer Akram
-
Update snapd helpers to execute over ssh
platform-qa-bot (platform-qa-bot) wrote : | # |
FAILED: Continuous integration, rev:21
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
- 22. By Omer Akram
-
Merge with trunk
- 23. By Omer Akram
-
pylint happy
platform-qa-bot (platform-qa-bot) wrote : | # |
FAILED: Continuous integration, rev:23
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
Omer Akram (om26er) wrote : | # |
I updated this branch to execute all commands over ssh. With paramiko we automagically get a persistent ssh connection, so the performance should not be a problem. The remote machine should have `expect` installed. Both snapd and snapcraft commands should work just fine over ssh. For store api calls, we need a small wrapper that builds curl commands based on our requirements to be run over ssh.
with that we can have a single way to run tests on all targets (kvm, bare metal, all-snap(in future), lxd or a tablet.)
TODO: run_command_
platform-qa-bot (platform-qa-bot) wrote : | # |
FAILED: Continuous integration, rev:23
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
Heber Parrucci (heber013) wrote : | # |
Looks good. Just minor comments
- 24. By Omer Akram
-
Maintain persistent ssh object, fix review comments
- 25. By Omer Akram
-
rename and move ssh runner function to ssh.py
Omer Akram (om26er) wrote : | # |
Replied inline.
platform-qa-bot (platform-qa-bot) wrote : | # |
FAILED: Continuous integration, rev:25
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
- 26. By Omer Akram
-
More readable calls
platform-qa-bot (platform-qa-bot) wrote : | # |
FAILED: Continuous integration, rev:26
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
- 27. By Omer Akram
-
fix some words
platform-qa-bot (platform-qa-bot) wrote : | # |
FAILED: Continuous integration, rev:27
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
- 28. By Omer Akram
-
ignore chrome driver from requirements
platform-qa-bot (platform-qa-bot) wrote : | # |
PASSED: Continuous integration, rev:28
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
Santiago Baldassin (sbaldassin) wrote : | # |
Looks good but I think the ssh approach does not entirely solve the issue we discussed. See inline
Omer Akram (om26er) wrote : | # |
Added a comment.
Omer Akram (om26er) wrote : | # |
Adding slightly less relevant comment.
- 29. By Omer Akram
-
Add protection against direct initialization of SSHClient
platform-qa-bot (platform-qa-bot) wrote : | # |
FAILED: Continuous integration, rev:29
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
- 30. By Omer Akram
-
Merge with trunk
platform-qa-bot (platform-qa-bot) wrote : | # |
PASSED: Continuous integration, rev:30
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
Heber Parrucci (heber013) wrote : | # |
See comments inline.
- 31. By Omer Akram
-
Add optional overriding of hostname and username to run commands
Omer Akram (om26er) wrote : | # |
Replied inline, also extended run_command to optionally take hostname and username as suggested in your code.
platform-qa-bot (platform-qa-bot) wrote : | # |
PASSED: Continuous integration, rev:31
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
Santiago Baldassin (sbaldassin) wrote : | # |
Reply inline. Let's talk about this over the phone before we get into an endless discussion
Omer Akram (om26er) wrote : | # |
Replied and lets chat.
Heber Parrucci (heber013) wrote : | # |
Reply inline. Let's talk offline
Sergio Cazzolato (sergio-j-cazzolato) wrote : | # |
Comment inline
- 32. By Omer Akram
-
Update to a SSHManager approach to keep a pool of active connections
platform-qa-bot (platform-qa-bot) wrote : | # |
PASSED: Continuous integration, rev:32
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
Omer Akram (om26er) wrote : | # |
Replied inline.
- 33. By Omer Akram
-
Add doc-string
Omer Akram (om26er) wrote : | # |
Sergio, replied inline.
platform-qa-bot (platform-qa-bot) wrote : | # |
PASSED: Continuous integration, rev:33
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
- 34. By Omer Akram
-
More simpler and compact approach
platform-qa-bot (platform-qa-bot) wrote : | # |
PASSED: Continuous integration, rev:34
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
- 35. By Omer Akram
-
reuse some code
platform-qa-bot (platform-qa-bot) wrote : | # |
PASSED: Continuous integration, rev:35
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
Heber Parrucci (heber013) wrote : | # |
Looks much better. Just a question inline.
Additionally, can you update README with remote host credentials info? you can reuse User Credentials section in that file if you wish.
- 36. By Omer Akram
-
update README for ssh credentials
Omer Akram (om26er) wrote : | # |
Updated README, added inline comments.
platform-qa-bot (platform-qa-bot) wrote : | # |
PASSED: Continuous integration, rev:36
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
Santiago Baldassin (sbaldassin) wrote : | # |
Looks good. Minor comments inline
Heber Parrucci (heber013) wrote : | # |
Reply to Santiago's suggestion
Omer Akram (om26er) wrote : | # |
Replied.
Omer Akram (om26er) wrote : | # |
re.
- 37. By Omer Akram
-
Changes
platform-qa-bot (platform-qa-bot) wrote : | # |
PASSED: Continuous integration, rev:37
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
Santiago Baldassin (sbaldassin) wrote : | # |
Looks Good. Thanks
Heber Parrucci (heber013) wrote : | # |
Code LGTM. Let's land it.
Preview Diff
1 | === modified file 'README.rst' | |||
2 | --- README.rst 2017-02-20 22:56:17 +0000 | |||
3 | +++ README.rst 2017-02-22 19:08:43 +0000 | |||
4 | @@ -69,10 +69,16 @@ | |||
5 | 69 | [user] | 69 | [user] |
6 | 70 | user_email=^USER_NAME^ | 70 | user_email=^USER_NAME^ |
7 | 71 | user_password=^USER_PASSWORD^ | 71 | user_password=^USER_PASSWORD^ |
8 | 72 | hostname_remote=^SSH_HOSTNAME^ | ||
9 | 73 | username_remote=^SSH_USERNAME^ | ||
10 | 74 | port_remote=^SSH_PORT^ | ||
11 | 72 | 75 | ||
12 | 73 | option 2 - Set the following environment variables: | 76 | option 2 - Set the following environment variables: |
13 | 74 | user_email=^USER_NAME^ | 77 | user_email=^USER_NAME^ |
14 | 75 | user_password=^USER_PASSWORD^ | 78 | user_password=^USER_PASSWORD^ |
15 | 79 | hostname_remote=^SSH_HOSTNAME^ | ||
16 | 80 | username_remote=^SSH_USERNAME^ | ||
17 | 81 | port_remote=^SSH_PORT^ | ||
18 | 76 | 82 | ||
19 | 77 | 83 | ||
20 | 78 | Changing store: | 84 | Changing store: |
21 | 79 | 85 | ||
22 | === modified file 'requirements.txt' | |||
23 | --- requirements.txt 2017-02-21 12:00:19 +0000 | |||
24 | +++ requirements.txt 2017-02-22 19:08:43 +0000 | |||
25 | @@ -16,3 +16,5 @@ | |||
26 | 16 | requests-toolbelt==0.6.0 | 16 | requests-toolbelt==0.6.0 |
27 | 17 | #chromedriver_installer | 17 | #chromedriver_installer |
28 | 18 | pylxd | 18 | pylxd |
29 | 19 | pyyaml | ||
30 | 20 | paramiko | ||
31 | 19 | 21 | ||
32 | === modified file 'snappy_ecosystem_tests/helpers/snapd/snapd.py' | |||
33 | --- snappy_ecosystem_tests/helpers/snapd/snapd.py 2017-02-09 20:09:40 +0000 | |||
34 | +++ snappy_ecosystem_tests/helpers/snapd/snapd.py 2017-02-22 19:08:43 +0000 | |||
35 | @@ -17,3 +17,140 @@ | |||
36 | 17 | # You should have received a copy of the GNU General Public License | 17 | # You should have received a copy of the GNU General Public License |
37 | 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
38 | 19 | # | 19 | # |
39 | 20 | |||
40 | 21 | """Helpers around snapd command-line interface.""" | ||
41 | 22 | |||
42 | 23 | import json | ||
43 | 24 | import logging | ||
44 | 25 | |||
45 | 26 | import yaml | ||
46 | 27 | |||
47 | 28 | from snappy_ecosystem_tests.utils import ssh | ||
48 | 29 | |||
49 | 30 | PATH_SNAP = '/usr/bin/snap' | ||
50 | 31 | COMMAND_DOWNLOAD = 'download {snap} --channel={channel}' | ||
51 | 32 | COMMAND_FIND = 'find {search_term}' | ||
52 | 33 | COMMAND_INFO = 'info {snap}' | ||
53 | 34 | COMMAND_INSTALL = 'install {snap} --channel={channel}' | ||
54 | 35 | COMMAND_LIST = 'list' | ||
55 | 36 | COMMAND_LOGIN = 'login {email}' | ||
56 | 37 | COMMAND_LOGOUT = 'logout' | ||
57 | 38 | COMMAND_REFRESH = 'refresh {snap} --channel={channel}' | ||
58 | 39 | COMMAND_REMOVE = 'remove {snap}' | ||
59 | 40 | CHANNEL_STABLE = 'stable' | ||
60 | 41 | COMMANDS_LOGIN = """\ | ||
61 | 42 | /usr/bin/expect \ | ||
62 | 43 | -c 'spawn snap login {email}' \ | ||
63 | 44 | -c 'expect \"Password*\"' \ | ||
64 | 45 | -c 'send {password}\\r' \ | ||
65 | 46 | -c 'interact'\ | ||
66 | 47 | """ | ||
67 | 48 | LOGGER = logging.getLogger(__name__) | ||
68 | 49 | |||
69 | 50 | |||
70 | 51 | def run_snapd_command_ssh(parameters, cwd=''): | ||
71 | 52 | """Run snapd command over ssh. | ||
72 | 53 | |||
73 | 54 | :param parameters: a string containing parameters for the `snap` command. | ||
74 | 55 | :param cwd: the current working directory on the remote where the command | ||
75 | 56 | should run. | ||
76 | 57 | :returns: stdout of the command | ||
77 | 58 | """ | ||
78 | 59 | if cwd: | ||
79 | 60 | return ssh.run_command( | ||
80 | 61 | 'cd {}; {} {}'.format(cwd, PATH_SNAP, parameters)) | ||
81 | 62 | return ssh.run_command('{} {}'.format(PATH_SNAP, parameters)) | ||
82 | 63 | |||
83 | 64 | |||
84 | 65 | def login(email, password): | ||
85 | 66 | """Login to snapd. | ||
86 | 67 | |||
87 | 68 | :param email: Ubuntu SSO account email address. | ||
88 | 69 | :param password: Ubuntu SSO account password. | ||
89 | 70 | """ | ||
90 | 71 | ssh.run_command(COMMANDS_LOGIN.format(email=email, password=password)) | ||
91 | 72 | return is_logged_in(email) | ||
92 | 73 | |||
93 | 74 | |||
94 | 75 | def is_logged_in(email): | ||
95 | 76 | """Return bool representing if the user is logged into snapd.""" | ||
96 | 77 | try: | ||
97 | 78 | return json.loads(ssh.run_command('cat ~/.snap/auth.json')).get( | ||
98 | 79 | 'email') == email | ||
99 | 80 | except ValueError: | ||
100 | 81 | return False | ||
101 | 82 | |||
102 | 83 | |||
103 | 84 | def logout(email): | ||
104 | 85 | """Logout snapd current user.""" | ||
105 | 86 | if is_logged_in(email): | ||
106 | 87 | run_snapd_command_ssh(COMMAND_LOGOUT) | ||
107 | 88 | |||
108 | 89 | |||
109 | 90 | def download(snap, channel=CHANNEL_STABLE): | ||
110 | 91 | """Download the requested snap. | ||
111 | 92 | |||
112 | 93 | :param snap: name of the snap to download. | ||
113 | 94 | :param channel: name of the release channel to download from. | ||
114 | 95 | """ | ||
115 | 96 | command = COMMAND_DOWNLOAD.format(snap=snap, channel=channel) | ||
116 | 97 | run_snapd_command_ssh(command, cwd=ssh.run_command('mktemp -d')) | ||
117 | 98 | |||
118 | 99 | |||
119 | 100 | def install(snap, channel=CHANNEL_STABLE): | ||
120 | 101 | """Install the requested snap.""" | ||
121 | 102 | run_snapd_command_ssh(COMMAND_INSTALL.format(snap=snap, channel=channel)) | ||
122 | 103 | |||
123 | 104 | |||
124 | 105 | def _is_installed(snap): | ||
125 | 106 | """Return bool representing whether a snap is installed.""" | ||
126 | 107 | for installed_snap in _parse_output(run_snapd_command_ssh(COMMAND_LIST)): | ||
127 | 108 | if installed_snap['name'] == snap: | ||
128 | 109 | return True | ||
129 | 110 | return False | ||
130 | 111 | |||
131 | 112 | |||
132 | 113 | def remove(snap): | ||
133 | 114 | """Remove a snap, if its already installed.""" | ||
134 | 115 | if _is_installed(snap): | ||
135 | 116 | run_snapd_command_ssh(COMMAND_REMOVE.format(snap=snap)) | ||
136 | 117 | |||
137 | 118 | |||
138 | 119 | def info(snap, verbose=False): | ||
139 | 120 | """Query the Ubuntu store of the information about a snap. | ||
140 | 121 | |||
141 | 122 | :param snap: Name of the snap for which the info is required. | ||
142 | 123 | :param verbose: Whether to information should be detailed. | ||
143 | 124 | :return: Return a dictionary containing information about the snap. | ||
144 | 125 | """ | ||
145 | 126 | command = COMMAND_INFO.format(snap=snap) | ||
146 | 127 | if verbose: | ||
147 | 128 | command = ' '.join([command, '--verbose']) | ||
148 | 129 | return yaml.load("""{}""".format(run_snapd_command_ssh(command))) | ||
149 | 130 | |||
150 | 131 | |||
151 | 132 | def refresh(snap, channel=CHANNEL_STABLE): | ||
152 | 133 | """Refresh the requested snap.""" | ||
153 | 134 | run_snapd_command_ssh(COMMAND_REFRESH.format(snap=snap, channel=channel)) | ||
154 | 135 | |||
155 | 136 | |||
156 | 137 | def _parse_output(raw_output): | ||
157 | 138 | """Pretty parse the output from snapd commands like `find` and `list`. | ||
158 | 139 | |||
159 | 140 | :param raw_output: The raw output returned from the command | ||
160 | 141 | :return: A list of dictionaries containing sorted results from the output. | ||
161 | 142 | """ | ||
162 | 143 | split_output = raw_output.split('\n') | ||
163 | 144 | headers = [header.lower() for header in split_output.pop(0).split()] | ||
164 | 145 | return [dict(zip(headers, line.split())) for line in split_output] | ||
165 | 146 | |||
166 | 147 | |||
167 | 148 | def find(keyword): | ||
168 | 149 | """Find snaps based on the provided filters | ||
169 | 150 | |||
170 | 151 | :param keyword: Keyword to use for the query. | ||
171 | 152 | :return: Return a list of dictionaries containing information about | ||
172 | 153 | snaps matching the `keyword`. | ||
173 | 154 | """ | ||
174 | 155 | return _parse_output( | ||
175 | 156 | run_snapd_command_ssh(COMMAND_FIND.format(search_term=keyword))) | ||
176 | 20 | 157 | ||
177 | === added file 'snappy_ecosystem_tests/tests/test_snapd.py' | |||
178 | --- snappy_ecosystem_tests/tests/test_snapd.py 1970-01-01 00:00:00 +0000 | |||
179 | +++ snappy_ecosystem_tests/tests/test_snapd.py 2017-02-22 19:08:43 +0000 | |||
180 | @@ -0,0 +1,42 @@ | |||
181 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
182 | 2 | |||
183 | 3 | # | ||
184 | 4 | # Snappy Ecosystem Tests | ||
185 | 5 | # Copyright (C) 2017 Canonical | ||
186 | 6 | # | ||
187 | 7 | # This program is free software: you can redistribute it and/or modify | ||
188 | 8 | # it under the terms of the GNU General Public License as published by | ||
189 | 9 | # the Free Software Foundation, either version 3 of the License, or | ||
190 | 10 | # (at your option) any later version. | ||
191 | 11 | # | ||
192 | 12 | # This program is distributed in the hope that it will be useful, | ||
193 | 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
194 | 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
195 | 15 | # GNU General Public License for more details. | ||
196 | 16 | # | ||
197 | 17 | # You should have received a copy of the GNU General Public License | ||
198 | 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
199 | 19 | # | ||
200 | 20 | |||
201 | 21 | """Snapd testcases.""" | ||
202 | 22 | |||
203 | 23 | import testtools | ||
204 | 24 | |||
205 | 25 | from snappy_ecosystem_tests.helpers.snapd import snapd | ||
206 | 26 | from snappy_ecosystem_tests.utils.storeconfig import get_store_credentials | ||
207 | 27 | |||
208 | 28 | |||
209 | 29 | class SnapdTestCase(testtools.TestCase): | ||
210 | 30 | """Tests for snapd.""" | ||
211 | 31 | def setUp(self): | ||
212 | 32 | super().setUp() | ||
213 | 33 | |||
214 | 34 | def test_info(self): | ||
215 | 35 | """Ensure the publisher of core snap in canonical.""" | ||
216 | 36 | self.assertTrue('canonical', snapd.info('core')['publisher']) | ||
217 | 37 | |||
218 | 38 | def test_login(self): | ||
219 | 39 | """Login the snapd user.""" | ||
220 | 40 | email, password = get_store_credentials() | ||
221 | 41 | self.assertTrue(snapd.login(email, password)) | ||
222 | 42 | self.addCleanup(snapd.logout, email) | ||
223 | 0 | 43 | ||
224 | === added file 'snappy_ecosystem_tests/utils/ssh.py' | |||
225 | --- snappy_ecosystem_tests/utils/ssh.py 1970-01-01 00:00:00 +0000 | |||
226 | +++ snappy_ecosystem_tests/utils/ssh.py 2017-02-22 19:08:43 +0000 | |||
227 | @@ -0,0 +1,92 @@ | |||
228 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
229 | 2 | |||
230 | 3 | # | ||
231 | 4 | # Snappy Ecosystem Tests | ||
232 | 5 | # Copyright (C) 2017 Canonical | ||
233 | 6 | # | ||
234 | 7 | # This program is free software: you can redistribute it and/or modify | ||
235 | 8 | # it under the terms of the GNU General Public License as published by | ||
236 | 9 | # the Free Software Foundation, either version 3 of the License, or | ||
237 | 10 | # (at your option) any later version. | ||
238 | 11 | # | ||
239 | 12 | # This program is distributed in the hope that it will be useful, | ||
240 | 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
241 | 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
242 | 15 | # GNU General Public License for more details. | ||
243 | 16 | # | ||
244 | 17 | # You should have received a copy of the GNU General Public License | ||
245 | 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
246 | 19 | # | ||
247 | 20 | |||
248 | 21 | """Module to connect to ssh client for running tests.""" | ||
249 | 22 | |||
250 | 23 | import paramiko | ||
251 | 24 | |||
252 | 25 | from snappy_ecosystem_tests.utils.user import get_remote_host_credentials | ||
253 | 26 | |||
254 | 27 | |||
255 | 28 | class SSHManager: | ||
256 | 29 | """Manager class to keep a pool of ssh connections.""" | ||
257 | 30 | # Mangle the pool so that it cannot be easily touched from outside. | ||
258 | 31 | __connection_pool = [] | ||
259 | 32 | |||
260 | 33 | def __init__(self): | ||
261 | 34 | raise NotImplementedError( | ||
262 | 35 | 'Class cannot be instantiated, use get_instance() to get a ' | ||
263 | 36 | 'SSHClient instance.') | ||
264 | 37 | |||
265 | 38 | @staticmethod | ||
266 | 39 | def get_instance(hostname, username, port=22, **kwargs): | ||
267 | 40 | """Return the instance of SSHClient from a pool of active connections, | ||
268 | 41 | otherwise create a new connection and return.""" | ||
269 | 42 | port = int(port) | ||
270 | 43 | client = SSHManager.get_client(hostname, username, port) | ||
271 | 44 | if client: | ||
272 | 45 | if not client.get_transport().is_active(): | ||
273 | 46 | client.connect( | ||
274 | 47 | hostname, username=username, port=port, **kwargs) | ||
275 | 48 | else: | ||
276 | 49 | client = paramiko.SSHClient() | ||
277 | 50 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) | ||
278 | 51 | client.connect(hostname, username=username, port=port, **kwargs) | ||
279 | 52 | SSHManager.__connection_pool.append(client) | ||
280 | 53 | return client | ||
281 | 54 | |||
282 | 55 | @staticmethod | ||
283 | 56 | def sync(): | ||
284 | 57 | """Update the connection pool to only keep alive connections.""" | ||
285 | 58 | pool_copy = SSHManager.__connection_pool.copy() | ||
286 | 59 | for client in SSHManager.__connection_pool: | ||
287 | 60 | if not client.get_transport().is_alive(): | ||
288 | 61 | pool_copy.remove(client) | ||
289 | 62 | SSHManager.__connection_pool = pool_copy | ||
290 | 63 | |||
291 | 64 | @staticmethod | ||
292 | 65 | def get_client(hostname, username, port): | ||
293 | 66 | """Return the matching client from the pool based on provided | ||
294 | 67 | constraints.""" | ||
295 | 68 | connections = [ | ||
296 | 69 | client for client in SSHManager.__connection_pool if | ||
297 | 70 | client.get_transport().getpeername() == (hostname, port) and | ||
298 | 71 | username == client.get_transport().get_username() | ||
299 | 72 | ] | ||
300 | 73 | return connections[0] if connections else None | ||
301 | 74 | |||
302 | 75 | |||
303 | 76 | def run_command(command, hostname=None, username=None, port=None): | ||
304 | 77 | """Run the given command on remote machine over ssh. | ||
305 | 78 | |||
306 | 79 | :param command: a string of the command to run. | ||
307 | 80 | :param hostname: The host to run command on. | ||
308 | 81 | :param username: Name of the user on the remote host to login to. | ||
309 | 82 | :param port: SSH port number. | ||
310 | 83 | :raises ValueError: if command exits with non-zero status. | ||
311 | 84 | :return: the stdout of the command. | ||
312 | 85 | """ | ||
313 | 86 | _hostname, _username, _port = get_remote_host_credentials() | ||
314 | 87 | ssh = SSHManager.get_instance( | ||
315 | 88 | hostname or _hostname, username or _username, port or _port) | ||
316 | 89 | _, stdout, stderr = ssh.exec_command(command) | ||
317 | 90 | if stdout.channel.recv_exit_status() != 0: | ||
318 | 91 | raise ValueError(stderr.read().decode()) | ||
319 | 92 | return stdout.read().decode() | ||
320 | 0 | 93 | ||
321 | === added file 'snappy_ecosystem_tests/utils/user.py' | |||
322 | --- snappy_ecosystem_tests/utils/user.py 1970-01-01 00:00:00 +0000 | |||
323 | +++ snappy_ecosystem_tests/utils/user.py 2017-02-22 19:08:43 +0000 | |||
324 | @@ -0,0 +1,35 @@ | |||
325 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
326 | 2 | |||
327 | 3 | # | ||
328 | 4 | # Snappy Ecosystem Tests | ||
329 | 5 | # Copyright (C) 2017 Canonical | ||
330 | 6 | # | ||
331 | 7 | # This program is free software: you can redistribute it and/or modify | ||
332 | 8 | # it under the terms of the GNU General Public License as published by | ||
333 | 9 | # the Free Software Foundation, either version 3 of the License, or | ||
334 | 10 | # (at your option) any later version. | ||
335 | 11 | # | ||
336 | 12 | # This program is distributed in the hope that it will be useful, | ||
337 | 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
338 | 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
339 | 15 | # GNU General Public License for more details. | ||
340 | 16 | # | ||
341 | 17 | # You should have received a copy of the GNU General Public License | ||
342 | 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
343 | 19 | # | ||
344 | 20 | |||
345 | 21 | """Get host user data.""" | ||
346 | 22 | |||
347 | 23 | import os | ||
348 | 24 | |||
349 | 25 | from snappy_ecosystem_tests.commons.config import USER_CONFIG_STACK | ||
350 | 26 | |||
351 | 27 | |||
352 | 28 | def get_remote_host_credentials(): | ||
353 | 29 | """Return credentials for remote machine, to run commands on.""" | ||
354 | 30 | return (USER_CONFIG_STACK.get('user', 'hostname_remote', | ||
355 | 31 | default=os.environ.get('hostname_remote')), | ||
356 | 32 | USER_CONFIG_STACK.get('user', 'username_remote', | ||
357 | 33 | default=os.environ.get('username_remote')), | ||
358 | 34 | USER_CONFIG_STACK.get('user', 'port_remote', | ||
359 | 35 | default=os.environ.get('port_remote'))) |
Please see my inline comments