Merge lp:~heber013/ubuntu-system-tests/adding-snap-test into lp:ubuntu-system-tests

Proposed by Heber Parrucci
Status: Merged
Merged at revision: 574
Proposed branch: lp:~heber013/ubuntu-system-tests/adding-snap-test
Merge into: lp:ubuntu-system-tests
Diff against target: 418 lines (+314/-14)
6 files modified
debian/tests/dependencies.json (+2/-0)
ubuntu_system_tests/helpers/snapd/__init__.py (+19/-0)
ubuntu_system_tests/helpers/snapd/snapd.py (+206/-0)
ubuntu_system_tests/host/target_setup.py (+21/-10)
ubuntu_system_tests/host/targets.py (+9/-4)
ubuntu_system_tests/tests/test_snapd.py (+57/-0)
To merge this branch: bzr merge lp:~heber013/ubuntu-system-tests/adding-snap-test
Reviewer Review Type Date Requested Status
platform-qa-bot continuous-integration Approve
Heber Parrucci (community) continuous-integration Needs Fixing
Review via email: mp+334762@code.launchpad.net

Commit message

Add tests for snapd command line: install, find, download a snap.

Description of the change

Add tests for snapd command line: install, find, download a snap.

@run_tests: ubuntu_system_tests.tests.test_snapd

To post a comment you must log in.
Revision history for this message
Heber Parrucci (heber013) wrote :
review: Approve (continuous-integration)
Revision history for this message
Heber Parrucci (heber013) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Heber Parrucci (heber013) wrote :
review: Approve (continuous-integration)
Revision history for this message
Heber Parrucci (heber013) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Heber Parrucci (heber013) wrote :
review: Approve (continuous-integration)
Revision history for this message
Heber Parrucci (heber013) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Heber Parrucci (heber013) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Heber Parrucci (heber013) wrote :
review: Approve (continuous-integration)
Revision history for this message
Heber Parrucci (heber013) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Heber Parrucci (heber013) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Heber Parrucci (heber013) wrote :
review: Approve (continuous-integration)
Revision history for this message
Heber Parrucci (heber013) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Heber Parrucci (heber013) wrote :
review: Approve (continuous-integration)
Revision history for this message
Heber Parrucci (heber013) wrote :
review: Needs Fixing (continuous-integration)
573. By Heber Parrucci

merge from trunk

Revision history for this message
platform-qa-bot (platform-qa-bot) wrote :
review: Needs Fixing (continuous-integration)
574. By Heber Parrucci

fixing json format for dependencies.json

Revision history for this message
platform-qa-bot (platform-qa-bot) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/tests/dependencies.json'
2--- debian/tests/dependencies.json 2017-12-22 19:35:15 +0000
3+++ debian/tests/dependencies.json 2018-01-02 13:43:13 +0000
4@@ -12,6 +12,7 @@
5 { "id": "python3-testtools", "version": "", "tmpinstall": "false" },
6 { "id": "python3-warlock", "version": "", "tmpinstall": "false" },
7 { "id": "python3-xlib", "version": "", "tmpinstall": "false" },
8+ { "id": "python3-yaml", "version": "", "tmpinstall": "false" },
9 { "id": "subunit", "version": "", "tmpinstall": "false" },
10 { "id": "ubuntu-app-launch-tools", "version": "", "tmpinstall": "false" },
11 { "id": "ubuntu-system-tests-helpers", "version": "", "tmpinstall": "false", "condition": "installed" },
12@@ -40,6 +41,7 @@
13 "python3-testtools",
14 "python3-warlock",
15 "python3-xlib",
16+ "python3-yaml",
17 "subunit",
18 "ubuntu-app-launch-tools",
19 "ubuntu-system-tests-helpers",
20
21=== added directory 'ubuntu_system_tests/helpers/snapd'
22=== added file 'ubuntu_system_tests/helpers/snapd/__init__.py'
23--- ubuntu_system_tests/helpers/snapd/__init__.py 1970-01-01 00:00:00 +0000
24+++ ubuntu_system_tests/helpers/snapd/__init__.py 2018-01-02 13:43:13 +0000
25@@ -0,0 +1,19 @@
26+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
27+
28+#
29+# Snappy Ecosystem Tests
30+# Copyright (C) 2017 Canonical
31+#
32+# This program is free software: you can redistribute it and/or modify
33+# it under the terms of the GNU General Public License as published by
34+# the Free Software Foundation, either version 3 of the License, or
35+# (at your option) any later version.
36+#
37+# This program is distributed in the hope that it will be useful,
38+# but WITHOUT ANY WARRANTY; without even the implied warranty of
39+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
40+# GNU General Public License for more details.
41+#
42+# You should have received a copy of the GNU General Public License
43+# along with this program. If not, see <http://www.gnu.org/licenses/>.
44+#
45
46=== added file 'ubuntu_system_tests/helpers/snapd/snapd.py'
47--- ubuntu_system_tests/helpers/snapd/snapd.py 1970-01-01 00:00:00 +0000
48+++ ubuntu_system_tests/helpers/snapd/snapd.py 2018-01-02 13:43:13 +0000
49@@ -0,0 +1,206 @@
50+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
51+
52+#
53+# Snappy Ecosystem Tests
54+# Copyright (C) 2017 Canonical
55+#
56+# This program is free software: you can redistribute it and/or modify
57+# it under the terms of the GNU General Public License as published by
58+# the Free Software Foundation, either version 3 of the License, or
59+# (at your option) any later version.
60+#
61+# This program is distributed in the hope that it will be useful,
62+# but WITHOUT ANY WARRANTY; without even the implied warranty of
63+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
64+# GNU General Public License for more details.
65+#
66+# You should have received a copy of the GNU General Public License
67+# along with this program. If not, see <http://www.gnu.org/licenses/>.
68+#
69+
70+"""Helpers around snapd command-line interface."""
71+
72+import json
73+import logging
74+from time import sleep
75+
76+import yaml
77+from ubuntu_system_tests.helpers.testbed import run_command_with_sudo, run_command
78+
79+PATH_SNAP = '/usr/bin/snap'
80+COMMAND_DOWNLOAD = 'download {snap} --channel={channel}'
81+COMMAND_FIND = 'find {search_term}'
82+COMMAND_INFO = 'info {snap}'
83+COMMAND_INSTALL = 'install {snap} --channel={channel}'
84+COMMAND_LIST = 'list'
85+COMMAND_LOGIN = 'login {email}'
86+COMMAND_LOGOUT = 'logout'
87+COMMAND_REFRESH = 'refresh {snap} --channel={channel}'
88+COMMAND_REMOVE = 'remove {snap}'
89+COMMAND_SIGN = 'sign'
90+CHANNEL_STABLE = 'stable'
91+COMMAND_LIST_KEYS = 'keys'
92+COMMANDS_LOGIN = """\
93+sudo /usr/bin/expect \
94+-c 'spawn snap login {email}' \
95+-c 'expect \"Password*\"' \
96+-c 'send {password}\\r' \
97+-c 'interact'\
98+"""
99+
100+
101+LOGGER = logging.getLogger(__name__)
102+
103+
104+class Snapd:
105+ """Contain Snapd specific functionality to use via command
106+ line interface"""
107+
108+ def run_snapd_command(self, command, cwd='', sudo=False):
109+ """Run snapd command over ssh.
110+
111+ :param command: a string containing parameters for the `snap` command.
112+ :param cwd: the current working directory on the remote where
113+ the command should run.
114+ :param sudo: whether to run the command with sudo privileges
115+ :returns: stdout of the command
116+ """
117+ if not command.startswith(PATH_SNAP):
118+ command = '{} {}'.format(PATH_SNAP, command)
119+ if cwd:
120+ command = 'cd {};{}'.format(cwd.rstrip(), command)
121+ if sudo:
122+ return run_command_with_sudo(command)
123+ else:
124+ return run_command(command)
125+
126+ def login(self, email, password):
127+ """Login to snapd.
128+
129+ :param email: Ubuntu SSO account email address.
130+ :param password: Ubuntu SSO account password.
131+ """
132+ run_command(COMMANDS_LOGIN.format(email=email,
133+ password=password))
134+ return self.is_logged_in(email)
135+
136+ def is_logged_in(self, email):
137+ """Return bool representing if the user is logged into snapd."""
138+ try:
139+ return json.loads(run_command('cat ~/.snap/auth.json')).get('email') == email
140+ except ValueError:
141+ return False
142+
143+ def logout(self, email):
144+ """Logout snapd current user."""
145+ if self.is_logged_in(email):
146+ self.run_snapd_command(COMMAND_LOGOUT)
147+
148+ def download(self, snap, channel=CHANNEL_STABLE):
149+ """Download the requested snap.
150+
151+ :param snap: name of the snap to download.
152+ :param channel: name of the release channel to download from.
153+ """
154+ command = COMMAND_DOWNLOAD.format(snap=snap, channel=channel)
155+ _dir = run_command('mktemp -d').rstrip()
156+ self.run_snapd_command(command, cwd=_dir)
157+ return _dir
158+
159+ def install(self, snap, channel=CHANNEL_STABLE):
160+ """Install the requested snap."""
161+ return self.run_snapd_command(COMMAND_INSTALL.format(snap=snap,
162+ channel=channel),
163+ sudo=True)
164+
165+ def is_installed(self, snap):
166+ """Return bool representing whether a snap is installed."""
167+ try:
168+ for installed_snap in Snapd._parse_output(
169+ self.run_snapd_command(COMMAND_LIST)):
170+ if installed_snap['name'] == snap:
171+ return True
172+ except KeyError:
173+ return False
174+ return False
175+
176+ def remove(self, snap):
177+ """Remove a snap, if its already installed."""
178+ if self.is_installed(snap):
179+ self.run_snapd_command(COMMAND_REMOVE.format(snap=snap), sudo=True)
180+
181+ def info(self, snap, verbose=False):
182+ """Query the Ubuntu store of the information about a snap.
183+
184+ :param snap: Name of the snap for which the info is required.
185+ :param verbose: Whether to information should be detailed.
186+ :return: Return a dictionary containing information about the snap.
187+ """
188+ command = COMMAND_INFO.format(snap=snap)
189+ if verbose:
190+ command = ' '.join([command, '--verbose'])
191+ return yaml.load("""{}""".format(self.run_snapd_command(command)))
192+
193+ def refresh(self, snap, channel=CHANNEL_STABLE):
194+ """Refresh the requested snap."""
195+ self.run_snapd_command(COMMAND_REFRESH.format(snap=snap,
196+ channel=channel))
197+
198+ @staticmethod
199+ def _parse_output(raw_output):
200+ """Pretty parse the output from snapd commands like `find` and `list`.
201+
202+ :param raw_output: The raw output returned from the command
203+ :return: A list of dictionaries containing sorted results
204+ from the output.
205+ """
206+ split_output = raw_output.split('\n')
207+ headers = [header.lower() for header in split_output.pop(0).split()]
208+ return [dict(zip(headers, line.split())) for line in split_output]
209+
210+ def find(self, keyword):
211+ """Find snaps based on the provided filters
212+
213+ :param keyword: Keyword to use for the query.
214+ :return: Return a list of dictionaries containing information about
215+ snaps matching the `keyword`.
216+ """
217+ return Snapd._parse_output(
218+ self.run_snapd_command(COMMAND_FIND.format(
219+ search_term=keyword)))
220+
221+ def is_published(self, snap_name, revision, channel='stable'):
222+ """Return bool representing whether a snap is published.
223+
224+ :param snap_name: Name of the snap to check.
225+ :param revision: Revision to check.
226+ :param channel: Channel to check.
227+ :return: True if published, False otherwise.
228+ """
229+ try:
230+ return int(self.info(snap_name)['channels'][channel].split()[1]
231+ .strip('()')) >= int(revision)
232+ except (ValueError, KeyError):
233+ return False
234+
235+ def wait_for_publish(self, snap_name, revision, channel='stable',
236+ retry_attempts=10, retry_interval=1):
237+ """Waits for the requested snap to publish by checking if its info
238+ can be retrieved.
239+
240+ :param snap_name: Name of the snap to wait for publish.
241+ :param revision: Snap revision number to check.
242+ :param channel: Channel to check snap in.
243+ :param retry_attempts: Number of times to check for the publish.
244+ :param retry_interval: Time between each attempt.
245+ :raises ValueError: If the snap is not found published after the
246+ given time.
247+ """
248+ for _ in range(retry_attempts):
249+ if self.is_published(snap_name, revision, channel):
250+ break
251+ else:
252+ sleep(retry_interval)
253+ else:
254+ raise ValueError('Snap not published, waited {} seconds.'.format(
255+ retry_attempts * retry_interval))
256
257=== modified file 'ubuntu_system_tests/host/target_setup.py'
258--- ubuntu_system_tests/host/target_setup.py 2017-09-22 13:51:52 +0000
259+++ ubuntu_system_tests/host/target_setup.py 2018-01-02 13:43:13 +0000
260@@ -65,8 +65,10 @@
261
262 """Class to run setup actions directly on the target device."""
263
264- def __init__(self, config):
265+ def __init__(self, config, snapd_https_proxy, snapd_http_proxy):
266 self.config = config
267+ self.snapd_https_proxy = snapd_https_proxy
268+ self.snapd_http_proxy = snapd_http_proxy
269 self.series = None
270 self.release = None
271 self.core_series = None
272@@ -94,6 +96,7 @@
273 with open("/etc/environment", "r") as environment:
274 if "QT_LOAD_TESTABILITY" not in environment.read():
275 self.enable_testability()
276+ self.setup_snapd_proxy()
277
278 def get_release(self):
279 """Return float release number of running system."""
280@@ -258,16 +261,19 @@
281 self.mount_fs_rw()
282 self._dist_upgrade()
283
284- def setup_snapd_https_proxy(self):
285- """Setup https proxy for snapd if its defined in config."""
286- proxy = self.config.get('https_proxy')
287- if proxy:
288+ def setup_snapd_proxy(self):
289+ """Setup proxy for snapd if it is defined in config."""
290+ if self.snapd_https_proxy or self.snapd_http_proxy:
291 conf = '/etc/systemd/system/snapd.service.d/proxy.conf'
292 self._create_folder(os.path.dirname(conf))
293+ content = '[Service]\nEnvironment=SNAPD_DEBUG=1\n'
294+ if self.snapd_https_proxy:
295+ content += 'Environment=https_proxy={}\n'.format(self.snapd_https_proxy)
296+ if self.snapd_http_proxy:
297+ content += 'Environment=http_proxy={}\n'.format(self.snapd_http_proxy)
298 self._create_file(
299 conf,
300- '[Service]\n'
301- 'Environment=https_proxy={}\n'.format(proxy))
302+ content)
303 subprocess.check_call(['systemctl', 'daemon-reload'])
304 subprocess.check_call(['systemctl', 'restart', 'snapd.service'])
305
306@@ -599,10 +605,10 @@
307 ['systemctl', 'enable', service_name])
308
309
310-def main(config_path, username):
311+def main(config_path, snapd_https_proxy, snapd_http_proxy, username):
312 with open(config_path, 'r') as f:
313 config_json = json.loads(f.read())
314- runner = SetupRunner(config_json)
315+ runner = SetupRunner(config_json, snapd_https_proxy, snapd_http_proxy)
316 try:
317 runner.run_setup_commands(username)
318 finally:
319@@ -612,7 +618,12 @@
320 if __name__ == '__main__':
321 PARSER = argparse.ArgumentParser(description='Setup target.')
322 PARSER.add_argument('--config_path', help='target config path')
323+ PARSER.add_argument('--snapd_https_proxy', help='snapd https proxy', default=None)
324+ PARSER.add_argument('--snapd_http_proxy', help='snapd http proxy', default=None)
325 PARSER.add_argument('--username',
326 help='Username to configure the environment')
327 ARGS = PARSER.parse_args()
328- sys.exit(main(ARGS.config_path, ARGS.username))
329+ sys.exit(main(ARGS.config_path,
330+ ARGS.snapd_https_proxy,
331+ ARGS.snapd_http_proxy,
332+ ARGS.username))
333
334=== modified file 'ubuntu_system_tests/host/targets.py'
335--- ubuntu_system_tests/host/targets.py 2017-08-10 14:11:03 +0000
336+++ ubuntu_system_tests/host/targets.py 2018-01-02 13:43:13 +0000
337@@ -180,10 +180,15 @@
338 def _run_target_setup(self):
339 self._push_setup_script()
340 self._push_setup_config()
341- return self.run_sudo(
342- '/usr/bin/python3 /tmp/target_setup.py --config_path /tmp/target_config.json '
343- '--username %s' % self.config_stack.get('device_username'),
344- timeout=TIMEOUT_SETUP)
345+ command = '/usr/bin/python3 /tmp/target_setup.py --config_path /tmp/target_config.json ' \
346+ '--username %s' % self.config_stack.get('device_username')
347+ snapd_https_proxy = self.config_stack.get('snapd_https_proxy')
348+ snapd_http_proxy = self.config_stack.get('snapd_http_proxy')
349+ if snapd_https_proxy:
350+ command += ' --snapd_https_proxy %s' % snapd_https_proxy
351+ if snapd_http_proxy:
352+ command += ' --snapd_http_proxy %s' % snapd_http_proxy
353+ return self.run_sudo(command, timeout=TIMEOUT_SETUP)
354
355 def get_config(self, config_stack, args):
356 """Select the required config items to pass to the target setup
357
358=== added file 'ubuntu_system_tests/tests/test_snapd.py'
359--- ubuntu_system_tests/tests/test_snapd.py 1970-01-01 00:00:00 +0000
360+++ ubuntu_system_tests/tests/test_snapd.py 2018-01-02 13:43:13 +0000
361@@ -0,0 +1,57 @@
362+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
363+
364+#
365+# Ubuntu System Tests
366+# Copyright (C) 2017 Canonical
367+#
368+# This program is free software: you can redistribute it and/or modify
369+# it under the terms of the GNU General Public License as published by
370+# the Free Software Foundation, either version 3 of the License, or
371+# (at your option) any later version.
372+#
373+# This program is distributed in the hope that it will be useful,
374+# but WITHOUT ANY WARRANTY; without even the implied warranty of
375+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
376+# GNU General Public License for more details.
377+#
378+# You should have received a copy of the GNU General Public License
379+# along with this program. If not, see <http://www.gnu.org/licenses/>.
380+#
381+import os
382+
383+from testtools import skip
384+
385+from ubuntu_system_tests.helpers.snapd.snapd import Snapd
386+from ubuntu_system_tests.tests.base import BaseUbuntuSystemTestCase
387+
388+
389+class TestSnapd(BaseUbuntuSystemTestCase):
390+ """Tests for snapd"""
391+
392+ def setUp(self):
393+ super(TestSnapd, self).setUp()
394+ self.snapd = Snapd()
395+ self.snap_name = 'hello-world'
396+
397+ def test_install_snap(self):
398+ """Install snap and verify it is installed properly"""
399+ self.snapd.install(self.snap_name)
400+ self.assertTrue(self.snapd.is_installed(self.snap_name),
401+ 'The snap %s is not installed' % self.snap_name)
402+
403+ def test_find_snap(self):
404+ """Find a snap and verify it is returned properly"""
405+ self.assertTrue(self.snapd.find(self.snap_name),
406+ 'The snap %s was not returned' % self.snap_name)
407+
408+ @skip('Skip until snap download issue is solved in venonat')
409+ def test_download_snap(self):
410+ """Download a snap and verify it is downloaded properly"""
411+ snap_dir = self.snapd.download(self.snap_name)
412+ self.assertTrue(len(os.listdir(snap_dir)) > 0,
413+ 'The snap %s was not downloaded' % self.snap_name)
414+
415+ def tearDown(self):
416+ super().tearDown()
417+ if self.snapd.is_installed(self.snap_name):
418+ self.snapd.remove(self.snap_name)

Subscribers

People subscribed via source and target branches