Merge lp:~psivaa/uci-engine/image-watcher into lp:uci-engine

Proposed by Para Siva
Status: Merged
Approved by: Para Siva
Approved revision: 931
Merged at revision: 923
Proposed branch: lp:~psivaa/uci-engine/image-watcher
Merge into: lp:uci-engine
Diff against target: 335 lines (+302/-0)
6 files modified
image-watcher/imagewatcher/__init__.py (+15/-0)
image-watcher/imagewatcher/queues.py (+34/-0)
image-watcher/imagewatcher/tests/test_trigger_process.py (+43/-0)
image-watcher/imagewatcher/tests/test_watch_image.py (+58/-0)
image-watcher/imagewatcher/trigger_process.py (+29/-0)
image-watcher/imagewatcher/watch_image.py (+123/-0)
To merge this branch: bzr merge lp:~psivaa/uci-engine/image-watcher
Reviewer Review Type Date Requested Status
Paul Larson Approve
Celso Providelo (community) Approve
Review via email: mp+246552@code.launchpad.net

Commit message

Image watcher to trigger the workflow for when a new image available in http://cloud-images.ubuntu.com/

Description of the change

Image watcher to trigger when there is a new image present in http://cloud-images.ubuntu.com/ubuntu-core/devel. The script checks the content of http://cloud-images.ubuntu.com/ubuntu-core/devel/core/current/build-info.txt to find if there is a new version in the server.

In terms of message passing to a queue, once we find a new image in the server we provide the version, channel and the test branch in the snappy.images queue for the next service to consume.

To post a comment you must log in.
lp:~psivaa/uci-engine/image-watcher updated
927. By Para Siva

Revert the unneeded amqp_utils change

928. By Para Siva

add a test to make sure the process triggers when started afresh

Revision history for this message
Celso Providelo (cprov) wrote :

Psivaa,

Thanks for working on this script.

I have nothing against monitoring build-info.txt instead of using simplestreams (python-simplestreams), as long as it works, as it seems to be. We can invest time later to rewrite the whole service to benefit of simplestreams goodies (supports the whole procedure from stream to glance).

There are some questions and remarks inline, specially regarding the topology of the new CLIs and how the charm is supposed to deploy them and its implication on configuration.

review: Needs Information
lp:~psivaa/uci-engine/image-watcher updated
929. By Para Siva

Review comment fixes, do not use specific config, and always use run-python

Revision history for this message
Para Siva (psivaa) wrote :

Thanks cprov for the comments.

I have removed using a special config file and relying on the amqp_utils.get_config() for the config information. For this as you pointed out, amqp-relation hook needs to be implemented in the cron-cmd charm.

Thanks again.

Revision history for this message
Celso Providelo (cprov) wrote :

Psivaa,

Thanks for the cleanup, I have just a minor suggestion about allowing image_watch.main() to be properly tested, but it's not a blocker.

Don't forget to coordinate with Joe for supporting amqp-relation in cron-cmd charm.

review: Approve
Revision history for this message
Paul Larson (pwlars) wrote :

> Psivaa,
>
> Thanks for working on this script.
>
> I have nothing against monitoring build-info.txt instead of using
> simplestreams (python-simplestreams), as long as it works, as it seems to be.
> We can invest time later to rewrite the whole service to benefit of
> simplestreams goodies (supports the whole procedure from stream to glance).
This is actually possible now, we discussed yesterday and I think we all agreed *not* to do that though right?

Revision history for this message
Paul Larson (pwlars) wrote :

Celso mentioned (if I understood correctly) in a MP for me that we should take all of these services for ubuntucore/snappy and organize them under a single directory rather than having an individual directory for each one off the root of our tree. I think this probably makes sense because they are all related to ubuntucore/snappy, rather than, for instance, a generic image watcher that may have to monitor some other type of source and handle things differently.

Some additional comments below

review: Needs Fixing
Revision history for this message
Celso Providelo (cprov) wrote :

Psivaa, Paul,

I retracted my concerns about having one directory per service (CLIs + worker python module), it the right direction.

With that tree organisation we run CLIs locally or via with `run-python.py` and properly configure rabbit-worker. To me, it looks perfectly fine and acceptable and if it needs rework we can deal with it later.

[]

review: Approve
lp:~psivaa/uci-engine/image-watcher updated
930. By Para Siva

Review comment fix, account for empty version number in the server

931. By Para Siva

Remove pep8 and pyflake testing locally

Revision history for this message
Para Siva (psivaa) wrote :

Thanks,
@plars: I have addressed your comments inline. and fixed where appropriate. About checking the latest against the glance image-list, we need to have a uniquely corresponding image in glance to the image in cloud-image. Pending that, I believe we only could check against a local cache. Please let me know what you think. Thanks

Revision history for this message
Paul Larson (pwlars) wrote :

I'm fine if we sort out that detail later. I believe we can just call the glance image whatever we want though. The image we download (file) will be something like: devel-core-amd64-disk1.img so maybe we could call it something like devel-core-amd64-${imageid} (ex. devel-core-amd64-20150115.1)

In light of the shift of direction though, I think I'd rather see this get merged and we can sort out those details later. It's easy enough to change things once they are in place, and you've done a great job of getting the ball rolling with this. Thanks for working on this!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added directory 'image-watcher'
=== added directory 'image-watcher/imagewatcher'
=== added file 'image-watcher/imagewatcher/__init__.py'
--- image-watcher/imagewatcher/__init__.py 1970-01-01 00:00:00 +0000
+++ image-watcher/imagewatcher/__init__.py 2015-01-16 11:11:59 +0000
@@ -0,0 +1,15 @@
1#!/usr/bin/env python
2# Ubuntu CI Engine
3# Copyright 2015 Canonical Ltd.
4
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU Affero General Public License version 3, as
7# published by the Free Software Foundation.
8
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU Affero General Public License for more details.
13
14# You should have received a copy of the GNU Affero General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
016
=== added file 'image-watcher/imagewatcher/queues.py'
--- image-watcher/imagewatcher/queues.py 1970-01-01 00:00:00 +0000
+++ image-watcher/imagewatcher/queues.py 2015-01-16 11:11:59 +0000
@@ -0,0 +1,34 @@
1# Ubuntu CI Engine
2# Copyright 2014 Canonical Ltd.
3
4# This program is free software: you can redistribute it and/or modify it
5# under the terms of the GNU Affero General Public License version 3, as
6# published by the Free Software Foundation.
7
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranties of
10# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11# PURPOSE. See the GNU Affero General Public License for more details.
12
13# You should have received a copy of the GNU Affero General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16import json
17
18
19from ci_utils import amqp_utils
20
21
22class PublisherQueue(object):
23 """A proxy to the queue library.
24
25 This implementation targets ci_utils/amqp_utils but can be replaced for
26 tests.
27 """
28
29 def __init__(self, queue_name):
30 self.queue_name = queue_name
31
32 def publish(self, msg):
33 amqp_utils.send(self.queue_name, json.dumps(msg),
34 raise_errors=True)
035
=== added directory 'image-watcher/imagewatcher/tests'
=== added file 'image-watcher/imagewatcher/tests/__init__.py'
=== added file 'image-watcher/imagewatcher/tests/test_trigger_process.py'
--- image-watcher/imagewatcher/tests/test_trigger_process.py 1970-01-01 00:00:00 +0000
+++ image-watcher/imagewatcher/tests/test_trigger_process.py 2015-01-16 11:11:59 +0000
@@ -0,0 +1,43 @@
1# Copyright 2015 Canonical Ltd.
2
3# This program is free software: you can redistribute it and/or modify it
4# under the terms of the GNU Affero General Public License version 3, as
5# published by the Free Software Foundation.
6
7# This program is distributed in the hope that it will be useful, but
8# WITHOUT ANY WARRANTY; without even the implied warranties of
9# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
10# PURPOSE. See the GNU Affero General Public License for more details.
11
12# You should have received a copy of the GNU Affero General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14
15import unittest
16
17
18from ucitests import assertions
19
20
21from imagewatcher import trigger_process
22from ci_utils.testing import fixtures
23
24
25class TestPost(unittest.TestCase):
26
27 def test_trigger_simple(self):
28 queue = fixtures.FakeTaskQueue('that_queue')
29 trigger_process.post(queue, 111, 'ubuntu-core/devel', 'lp:fake')
30 assertions.assertLength(self, 1, queue.msgs)
31 self.assertEqual(dict(channel='ubuntu-core/devel', revision=111,
32 test_branch='lp:fake'),
33 queue.msgs[0])
34
35 def test_trigger_with_output(self):
36 queue = fixtures.FakeTaskQueue('that_queue')
37 trigger_process.post(queue, 111, 'ubuntu-core/devel', 'lp:fake',
38 'this_queue')
39 assertions.assertLength(self, 1, queue.msgs)
40 self.assertEqual(dict(channel='ubuntu-core/devel', revision=111,
41 test_branch='lp:fake',
42 output_queue='this_queue'),
43 queue.msgs[0])
044
=== added file 'image-watcher/imagewatcher/tests/test_watch_image.py'
--- image-watcher/imagewatcher/tests/test_watch_image.py 1970-01-01 00:00:00 +0000
+++ image-watcher/imagewatcher/tests/test_watch_image.py 2015-01-16 11:11:59 +0000
@@ -0,0 +1,58 @@
1# Ubuntu CI Engine
2# Copyright 2015 Canonical Ltd.
3
4# This program is free software: you can redistribute it and/or modify it
5# under the terms of the GNU Affero General Public License version 3, as
6# published by the Free Software Foundation.
7
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranties of
10# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11# PURPOSE. See the GNU Affero General Public License for more details.
12
13# You should have received a copy of the GNU Affero General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16import unittest
17import os
18
19from imagewatcher import watch_image
20
21
22class TestWatchImage(unittest.TestCase):
23 def setUp(self):
24 super(TestWatchImage, self).setUp()
25 self.fake_resp = [u'VERSION=112',
26 u'BUILD_TYPE=ubuntu-core/devel',
27 u'BUILD_DATE=20150109',
28 u'IMG_FORMAT=qcow2',
29 u'IMG_COMPAT=0.10',
30 u'DEVICE=generic_amd64',
31 u'SERIAL=20150109.2',
32 u'BUILD_JOB_ID=35',
33 u'']
34
35 def test_record_and_return_latest_version(self):
36 with open('/tmp/fake', 'w') as f:
37 f.write('111')
38 ret_val = watch_image.record_and_return('/tmp/fake', self.fake_resp)
39 os.remove('/tmp/fake')
40 self.assertEquals(ret_val, ('112', u'ubuntu-core/devel'))
41
42 def test_do_not_record_and_return_non_latest(self):
43 with open('/tmp/fake2', 'w') as f:
44 f.write('113')
45 ret_val = watch_image.record_and_return('/tmp/fake2', self.fake_resp)
46 os.remove('/tmp/fake2')
47 self.assertEquals(ret_val, None)
48
49 def test_freshly_record_and_return_latest_version(self):
50 if os.path.exists('/tmp/fake3'):
51 os.remove('/tmp/fake3')
52 ret_val = watch_image.record_and_return('/tmp/fake3', self.fake_resp)
53 os.remove('/tmp/fake3')
54 self.assertEquals(ret_val, ('112', u'ubuntu-core/devel'))
55
56 def test_get_latest_from_server_failed(self):
57 with self.assertRaises(watch_image.ImageWatcherError):
58 watch_image._get_latest_from_server('http://fake', retry=1)
059
=== added file 'image-watcher/imagewatcher/trigger_process.py'
--- image-watcher/imagewatcher/trigger_process.py 1970-01-01 00:00:00 +0000
+++ image-watcher/imagewatcher/trigger_process.py 2015-01-16 11:11:59 +0000
@@ -0,0 +1,29 @@
1#!/usr/bin/env python
2# Ubuntu CI Engine
3# Copyright 2015 Canonical Ltd.
4
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU Affero General Public License version 3, as
7# published by the Free Software Foundation.
8
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU Affero General Public License for more details.
13
14# You should have received a copy of the GNU Affero General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17from imagewatcher import queues
18
19
20def post(queue, revision, channel, test_branch, output=None):
21 msg = dict(revision=revision, channel=channel, test_branch=test_branch)
22 if output is not None:
23 msg['output_queue'] = output
24 queue.publish(msg)
25
26
27def trigger(revision, channel, test_branch, output_queue):
28 queue = queues.PublisherQueue('snappy.images')
29 post(queue, revision, channel, test_branch, output_queue)
030
=== added file 'image-watcher/imagewatcher/watch_image.py'
--- image-watcher/imagewatcher/watch_image.py 1970-01-01 00:00:00 +0000
+++ image-watcher/imagewatcher/watch_image.py 2015-01-16 11:11:59 +0000
@@ -0,0 +1,123 @@
1#!/usr/bin/env python
2# Ubuntu CI Engine
3# Copyright 2015 Canonical Ltd.
4
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU Affero General Public License version 3, as
7# published by the Free Software Foundation.
8
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU Affero General Public License for more details.
13
14# You should have received a copy of the GNU Affero General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import argparse
18import logging
19import requests
20import time
21import os
22import sys
23
24from uciconfig import errors
25
26from imagewatcher import (
27 trigger_process,
28)
29
30log = logging.getLogger('ImageWatcher')
31
32
33class ImageWatcherError(Exception):
34 """Raised if there was an error watching the new image."""
35
36
37def _get_args(args=None):
38 parser = argparse.ArgumentParser(description='Check if a new core-image is\
39 available at a given url')
40 parser.add_argument(
41 "-u", "--url",
42 default="http://cloud-images.ubuntu.com/ubuntu-core/devel/"
43 "core/current/build-info.txt",
44 help="URL to check if a new core image available"),
45 parser.add_argument(
46 "-l", "--location",
47 default="/tmp/latest-core-image-version",
48 help="The file containing latest image version")
49 parser.add_argument(
50 "-t", "--test-branch",
51 default="lp:snappy-tests",
52 help="Branch where the dep8 tests live")
53 parser.add_argument(
54 "-o", "--output-queue",
55 default=None,
56 help='Send progress/results from the image watcher to the queue'
57 ' named output_queue.')
58 return parser.parse_args(args)
59
60
61def _get_latest_from_server(url, retry=3):
62 try:
63 response = requests.get(url)
64 response.raise_for_status()
65 except (requests.ConnectionError, requests.HTTPError) as e:
66 if retry == 1:
67 raise ImageWatcherError(
68 'Error downloading the metadata {}'.format(str(e)))
69 log.warn('Page download failed, retrying in 30 sec')
70 time.sleep(30)
71 return _get_latest_from_server(url, retry - 1)
72 return response.text.split('\n')
73
74
75def record_and_return(location, response):
76 content = {}
77 for line in response:
78 if '=' in line:
79 k, v = line.strip().split('=')
80 content[k.strip()] = v.strip()
81 if content['VERSION'] == '':
82 log.error('The version number in the cloud-image server'
83 ' is not an integer but {}'.format(content['VERSION']))
84 sys.exit(1)
85
86 version = content['VERSION']
87 channel = content['BUILD_TYPE']
88 try:
89 if os.path.exists(location):
90 with open(location, 'r') as cache:
91 if (cache.read() < version):
92 with open(location, 'w') as f:
93 f.write(version)
94 return version, channel
95 else:
96 with open(location, 'w') as f:
97 f.write(version)
98 return version, channel
99 except IOError as e:
100 log.error('Writing the latest image info failed')
101 raise ImageWatcherError(
102 'Error writing the latest version with {}'.format(str(e)))
103
104
105def main(args=None):
106 args = _get_args(args=args)
107 try:
108 ret = record_and_return(
109 args.location,
110 _get_latest_from_server(args.url))
111 if ret:
112 trigger_process.trigger(ret[0],
113 ret[1],
114 args.test_branch,
115 args.output_queue)
116 else:
117 log.info('No new image found at {}'.format(time.asctime()))
118 except errors.ConfigError as e:
119 log.error('Triggering the workflow failed with {}'.format(e))
120 sys.exit(1)
121
122if __name__ == '__main__':
123 main()

Subscribers

People subscribed via source and target branches