Merge lp:~cprov/uci-engine/1335753-glance-creds into lp:uci-engine

Proposed by Celso Providelo
Status: Needs review
Proposed branch: lp:~cprov/uci-engine/1335753-glance-creds
Merge into: lp:uci-engine
Diff against target: 332 lines (+203/-32)
5 files modified
ci-utils/ci_utils/tests/test_unit_config.py (+125/-0)
ci-utils/ci_utils/unit_config.py (+57/-16)
juju-deployer/configs/unit_config.yaml.tmpl (+13/-5)
juju-deployer/deploy.py (+6/-6)
juju-deployer/test_deploy.py (+2/-5)
To merge this branch: bzr merge lp:~cprov/uci-engine/1335753-glance-creds
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Needs Fixing
Andy Doan (community) Approve
Vincent Ladeuil (community) Needs Fixing
Review via email: mp+225248@code.launchpad.net

Commit message

Independent set of credentials for ImageBuilder and TestRunner in unit_config.

Description of the change

Resuming Vincent's work for creating an independent set of credentials for using in ImageBuilder and TestRunner, since they may operate in an different environment (cloud) than they are deployed.

Original MP -> https://code.launchpad.net/~vila/uci-engine/1335753-glance-creds/+merge/225191

To post a comment you must log in.
634. By Celso Providelo

adding missing test.

635. By Celso Providelo

Adding missing docstring.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:635
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/990/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/990/rebuild

review: Needs Fixing (continuous-integration)
636. By Celso Providelo

Fixing test failures.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:636
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/992/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/992/rebuild

review: Approve (continuous-integration)
Revision history for this message
Vincent Ladeuil (vila) wrote :
Download full text (9.5 KiB)

Typos aside, there is an issue with tests creating and removing unit_config at the root of the branch.

With a full deployment available on hp and GLANCE_* vars defined, I get:

$ ./run-tests
<...>

======================================================================
ERROR: tests.test_test_runner.TestTestRunner.test_process_ticket
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tests/test_test_runner.py", line 90, in test_process_ticket
    raise_errors=True)
  File "/home/vila/ci/uci-engine/reviews/1335753-glance-creds/ci-utils/ci_utils/amqp_utils.py", line 99, in send
    conn, channel = declare_queue(queue_name, config)
  File "/home/vila/ci/uci-engine/reviews/1335753-glance-creds/ci-utils/ci_utils/amqp_utils.py", line 83, in declare_queue
    raise e
error: [Errno 110] Connection timed out
======================================================================
FAIL: juju-deployer.test_deploy.TestCheckEnvironment.test_check_environment_defaults
----------------------------------------------------------------------
Traceback (most recent call last):
  File "build/bdist.linux-x86_64/egg/mock.py", line 1201, in patched
    return func(*args, **keywargs)
  File "juju-deployer/test_deploy.py", line 77, in test_check_environment_defaults
    self.assertEqual('', os.environ['GLANCE_OS_USERNAME'])
  File "/usr/lib/python2.7/unittest/case.py", line 515, in assertEqual
    assertion_func(first, second, msg=msg)
  File "/usr/lib/python2.7/unittest/case.py", line 508, in _baseAssertEqual
    raise self.failureException(msg)
AssertionError: '' != '<email address hidden>'

Ran 594 tests in 501.909s
FAILED (failures=2)

test_process_tickey is now understood as missing an exposed rabbit.

But re-running:
$ ./run-tests
<...>
======================================================================
ERROR: test_runner.tstrun.tests.test_data_store.TestDataStore.test_put_file_stores_content
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_runner/tstrun/tests/test_data_store.py", line 62, in setUp
    tstrun.get_auth_config(), public=True)
  File "test_runner/tstrun/__init__.py", line 28, in get_auth_config
    with open(path) as f:
IOError: [Errno 2] No such file or directory: '/home/vila/ci/uci-engine/reviews/1335753-glance-creds/unit_config'
======================================================================
ERROR: test_runner.tstrun.tests.test_data_store.TestDataStore.test_store_created_empty
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_runner/tstrun/tests/test_data_store.py", line 62, in setUp
    tstrun.get_auth_config(), public=True)
  File "test_runner/tstrun/__init__.py", line 28, in get_auth_config
    with open(path) as f:
IOError: [Errno 2] No such file or directory: '/home/vila/ci/uci-engine/reviews/1335753-glance-creds/unit_config'
======================================================================
ERROR: test_runner.tstrun.tests.test_testbed.TestTestbed.test_create_new_ssh_key
-----------------------------------------------------...

Read more...

review: Needs Fixing
Revision history for this message
Vincent Ladeuil (vila) wrote :

I'll dig further and try to fix those.

637. By Celso Providelo

merge trunk

638. By Celso Providelo

FIxing typos and test env var isolation.

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

The fact that we have tests that depend on 'unit_config' on the project root is wrong, in fact. If it's not committed in the VCS it should be handled ad-hoc by a testing fixture.

Also, the only test that needs to exercise disk operations with 'unit_config' is the one I've added in this branch, all the rest of the system should mock get_auth_config() and use whatever result they expect.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:638
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/996/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/996/rebuild

review: Approve (continuous-integration)
Revision history for this message
Vincent Ladeuil (vila) wrote :

> The fact that we have tests that depend on 'unit_config' on the project root
> is wrong, in fact.
> If it's not committed in the VCS it should be handled ad-
> hoc by a testing fixture.

There is more than one way to do it :)

The tests that rely on unit_config do use fixtures but still let the user decide whether or not he want to run those tests by creating that file or not.

If you have a better way that doesn't break the existing tests, I'm fine with that.

>
> Also, the only test that needs to exercise disk operations with 'unit_config'
> is the one I've added in this branch, all the rest of the system should mock
> get_auth_config() and use whatever result they expect.

Yes, that's the point I raised when you introduced deploy.install_unit_config()... I don't know which tests required that.

Revision history for this message
Andy Doan (doanac) wrote :

I mostly like it. I noticed a bug a though. I think any test failing because it assumes the location of the unit-config should be considered broken. I've got MP's on the way:

 https://code.launchpad.net/~doanac/uci-engine/lander-code-layout/+merge/225046

that are placing unit_config *outside* our code directory, so moving forward this assumption will definitely not work.

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

Thanks for the review Andy, comments addressed inline.

639. By Celso Providelo

Re-implement unit_config module get() for not breaking possibly untested code.

Revision history for this message
Andy Doan (doanac) :
review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:639
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1001/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1001/rebuild

review: Approve (continuous-integration)
640. By Celso Providelo

unit_config.get() not deprecated anymore, it's useful.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:640
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1006/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1006/rebuild

review: Approve (continuous-integration)
641. By Celso Providelo

merge trunk

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:641
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1017/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1017/rebuild

review: Needs Fixing (continuous-integration)

Unmerged revisions

641. By Celso Providelo

merge trunk

640. By Celso Providelo

unit_config.get() not deprecated anymore, it's useful.

639. By Celso Providelo

Re-implement unit_config module get() for not breaking possibly untested code.

638. By Celso Providelo

FIxing typos and test env var isolation.

637. By Celso Providelo

merge trunk

636. By Celso Providelo

Fixing test failures.

635. By Celso Providelo

Adding missing docstring.

634. By Celso Providelo

adding missing test.

633. By Celso Providelo

Making GLANCE_* variables optional and adding tests for ci_utils.unit_config.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'ci-utils/ci_utils/tests/test_unit_config.py'
2--- ci-utils/ci_utils/tests/test_unit_config.py 1970-01-01 00:00:00 +0000
3+++ ci-utils/ci_utils/tests/test_unit_config.py 2014-07-04 21:48:29 +0000
4@@ -0,0 +1,125 @@
5+#!/usr/bin/env python
6+# Ubuntu CI Engine
7+# Copyright 2014 Canonical Ltd.
8+
9+# This program is free software: you can redistribute it and/or modify it
10+# under the terms of the GNU Affero General Public License version 3, as
11+# published by the Free Software Foundation.
12+
13+# This program is distributed in the hope that it will be useful, but
14+# WITHOUT ANY WARRANTY; without even the implied warranties of
15+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
16+# PURPOSE. See the GNU Affero General Public License for more details.
17+
18+# You should have received a copy of the GNU Affero General Public License
19+# along with this program. If not, see <http://www.gnu.org/licenses/>.
20+"""Tests for the 'unit_config' loader."""
21+import os
22+import unittest
23+
24+from ci_utils import unit_config
25+
26+
27+class TestUnitConfig(unittest.TestCase):
28+
29+ sample_content = {
30+ 'auth_user': 'a_user',
31+ }
32+
33+ def setUp(self):
34+ """Reset configuration cache."""
35+ unit_config._reset_config()
36+ self.config_path = unit_config._get_config_path()
37+
38+ def test_asserts_exists(self):
39+ # `get_auth_config` asserts the 'unit_config' path exists.
40+ with self.assertRaises(AssertionError) as cm:
41+ unit_config.get_auth_config()
42+ msg = 'unit_config does not exist! ({})'.format(self.config_path)
43+ self.assertEqual(msg, cm.exception.message)
44+
45+ def test_asserts_not_empty(self):
46+ # `get_auth_config` asserts the 'unit_config' is not an empty file.
47+ with open(self.config_path, 'w') as fd:
48+ fd.write('')
49+ with self.assertRaises(AssertionError) as cm:
50+ unit_config.get_auth_config()
51+ msg = 'unit_config is empty! ({})'.format(self.config_path)
52+ self.assertEqual(msg, cm.exception.message)
53+
54+ def create_config(self, content=None):
55+ """Creates a 'unit_config' pseudo-YAML for tests."""
56+ options = self.sample_content.copy()
57+ if content is not None:
58+ options.update(content)
59+ with open(self.config_path, 'w') as fd:
60+ for k, v in options.iteritems():
61+ fd.write('{}: {}\n'.format(k, v))
62+
63+ def drop_if_exists():
64+ if os.path.exists(self.config_path):
65+ os.remove(self.config_path)
66+ # Delete testing config if it exists.
67+ self.addCleanup(drop_if_exists)
68+
69+ def test_loading(self):
70+ # `get_auth_config` returns a dictionary corresponding to the
71+ # YAML content. 'auth_*' attributes are mirrored to 'glance_auth_*'
72+ self.create_config()
73+ config = unit_config.get_auth_config()
74+ self.assertEqual(
75+ {'auth_user': 'a_user', 'glance_auth_user': 'a_user'}, config)
76+
77+ def test_get(self):
78+ # `get` returns specific configuration options/attributes.
79+ self.create_config()
80+ self.assertEqual('a_user', unit_config.get('auth_user'))
81+ self.assertEqual('a_user', unit_config.get('glance_auth_user'))
82+
83+ def test_loading_glance(self):
84+ # `get_auth_config` preserves original 'glance_auth_*'
85+ # attributes if 'glance_auth_user' is not empty.
86+ glance_spec = {
87+ 'auth_url': 'a_url',
88+ 'glance_auth_user': 'another_user',
89+ 'glance_auth_url': 'another_url',
90+ }
91+ self.create_config(content=glance_spec)
92+ config = unit_config.get_auth_config()
93+ self.assertEqual(
94+ {'auth_url': 'a_url',
95+ 'auth_user': 'a_user',
96+ 'glance_auth_url': 'another_url',
97+ 'glance_auth_user': 'another_user'},
98+ config)
99+
100+ def test_cache(self):
101+ # `get_auth_config` loads configuration file contents only once
102+ # and caches it's (parsed) results. `_reset_config` is a testing
103+ # artifact for cleaning its cache (use with care!).
104+ self.create_config()
105+ config = unit_config.get_auth_config()
106+ self.create_config(content={'auth_user': 'fresh'})
107+ cached = unit_config.get_auth_config()
108+ self.assertEqual(cached, config)
109+ unit_config._reset_config()
110+ fresh = unit_config.get_auth_config()
111+ self.assertEqual(
112+ {'auth_user': 'fresh', 'glance_auth_user': 'fresh'},
113+ fresh)
114+
115+ def test_no_cache_poisoning(self):
116+ # `get_auth_config` returns a copy of the configuration cache,
117+ # so its cache cannot be *poisoned*.
118+ self.create_config()
119+ config = unit_config.get_auth_config()
120+ config['auth_user'] = 'someone-else'
121+ config['auth_poison'] = 'bad-data'
122+ new_config = unit_config.get_auth_config()
123+ self.assertEqual(
124+ {'auth_user': 'a_user', 'glance_auth_user': 'a_user'},
125+ new_config)
126+
127+
128+if __name__ == "__main__":
129+ unittest.main()
130
131=== modified file 'ci-utils/ci_utils/unit_config.py'
132--- ci-utils/ci_utils/unit_config.py 2014-05-14 09:11:00 +0000
133+++ ci-utils/ci_utils/unit_config.py 2014-07-04 21:48:29 +0000
134@@ -12,35 +12,76 @@
135
136 # You should have received a copy of the GNU Affero General Public License
137 # along with this program. If not, see <http://www.gnu.org/licenses/>.
138-
139+"""Application 'unit_config' loader."""
140 import os
141-
142 import yaml
143
144
145 HERE = os.path.abspath(os.path.dirname(__file__))
146-
147-
148 _unit_config = None
149
150
151-def _load():
152- path = os.path.join(HERE, '../../unit_config')
153+def _reset_config():
154+ """Testing utility for reseting unit_config cache."""
155+ global _unit_config
156+ _unit_config = None
157+
158+
159+def _get_config_path():
160+ """Testing utility for retrieving the configuration path."""
161+ return os.path.join(HERE, '../../unit_config')
162+
163+
164+def _load_config():
165+ """Load the configuration file as YAML and cache it.
166+
167+ Asserts the expected 'unit_config' exists and contains valid YAML.
168+ """
169+ global _unit_config
170+ path = _get_config_path()
171+ assert os.path.exists(path), (
172+ 'unit_config does not exist! ({})'.format(path))
173 with open(path) as f:
174- global _unit_config
175 _unit_config = yaml.safe_load(f)
176+ assert _unit_config is not None, (
177+ 'unit_config is empty! ({})'.format(path))
178+
179+
180+def get_auth_config():
181+ """Return a configuration dictionary for applications.
182+
183+ Loads the deployment 'unit_config' YAML file, if it wasn't loaded
184+ before, and return the corresponding python dictionary.
185+
186+ If 'glance_auth_user' is undefined (empty), 'glance_*' parameters
187+ are filled with the corresponding 'auth_*' values, i.e., GLANCE_*
188+ variables only have to be defined when the ci-airline 'testbed'
189+ environment is different than where the system is deployed e.g.:
190+ - stagingstack + scalingstack
191+ - stagingstack + hpcloud
192+ """
193+ global _unit_config
194+ if _unit_config is None:
195+ _load_config()
196+
197+ # Apply configuration defaults/overrides in a copy of the cached
198+ # configuration dictionary. This way applications will not be able
199+ # to poisoning the cached value.
200+ cfg = _unit_config.copy()
201+
202+ # 'glance_*' defaults.
203+ if cfg.get('glance_auth_user') is None:
204+ auth_keys = [k for k in cfg.keys() if k.startswith('auth_')]
205+ for auth_key in auth_keys:
206+ glance_key = 'glance_' + auth_key
207+ cfg[glance_key] = cfg[auth_key]
208+
209+ return cfg
210
211
212 def get(key):
213- if not _unit_config:
214- _load()
215- return _unit_config[key]
216-
217-
218-def get_auth_config():
219- if not _unit_config:
220- _load()
221- return _unit_config
222+ """Get specific 'unit_config' options."""
223+ return get_auth_config()[key]
224
225
226 def is_hpcloud(string=None):
227
228=== modified file 'juju-deployer/configs/unit_config.yaml.tmpl'
229--- juju-deployer/configs/unit_config.yaml.tmpl 2014-07-01 15:58:10 +0000
230+++ juju-deployer/configs/unit_config.yaml.tmpl 2014-07-04 21:48:29 +0000
231@@ -1,26 +1,34 @@
232-# for nova direct access (swift, image builder, test runner among others)
233+#--------------------------------------------------------------
234+# This file is managed by Juju; ANY CHANGES WILL BE OVERWRITTEN
235+#--------------------------------------------------------------
236+# CI-Airline unit configuration file.
237+
238+# Options for system deployment and common Swift access.
239 auth_url: $OS_AUTH_URL
240 auth_user: $OS_USERNAME
241 auth_password: $OS_PASSWORD
242 auth_tenant_name: $OS_TENANT_NAME
243 auth_region: $OS_REGION_NAME
244
245+# Options for Gatekeeper Swift tempurl generation and upload
246+# signature validation.
247 auth_access_key_id: $HP_ACCESS_KEY_ID
248 auth_tenant_id: $HP_TENANT_ID
249 tempurl_signing_key: $CI_TEMPURL_SIGNING_KEY
250 tempurl_expires: $CI_TEMPURL_EXPIRES
251 tempurl_public_host: $CI_TEMPURL_PUBLIC_HOST
252+keyring_uri: $CI_KEYRING_URI
253+keyserver_url: $CI_KEYSERVER_URL
254
255+# Options for acessing a isolated environment for
256+# Image-builder (Glance) and Test-runner (Nova).
257+# If not provided (empty), the system environment is used.
258 glance_auth_url: $GLANCE_OS_AUTH_URL
259 glance_auth_user: $GLANCE_OS_USERNAME
260 glance_auth_password: $GLANCE_OS_PASSWORD
261 glance_auth_tenant_name: $GLANCE_OS_TENANT_NAME
262 glance_auth_region: $GLANCE_OS_REGION_NAME
263
264-
265-keyring_uri: $CI_KEYRING_URI
266-keyserver_url: $CI_KEYSERVER_URL
267-
268 # for launchpad apis
269 # and see: https://help.launchpad.net/API/SigningRequests
270 # and see ../../create_lp_creds.py
271
272=== modified file 'juju-deployer/deploy.py'
273--- juju-deployer/deploy.py 2014-07-03 17:25:58 +0000
274+++ juju-deployer/deploy.py 2014-07-04 21:48:29 +0000
275@@ -94,11 +94,11 @@
276 '''
277 cheetah_vars = {
278 'OS_USERNAME': None,
279- 'GLANCE_OS_USERNAME': None,
280- 'GLANCE_OS_AUTH_URL': None,
281- 'GLANCE_OS_PASSWORD': None,
282- 'GLANCE_OS_TENANT_NAME': None,
283- 'GLANCE_OS_REGION_NAME': None,
284+ 'GLANCE_OS_USERNAME': '',
285+ 'GLANCE_OS_AUTH_URL': '',
286+ 'GLANCE_OS_PASSWORD': '',
287+ 'GLANCE_OS_TENANT_NAME': '',
288+ 'GLANCE_OS_REGION_NAME': '',
289 'CI_CODE_SOURCE': 'branch',
290 'CI_BRANCH': 'lp:uci-engine',
291 'CI_PAYLOAD_URL': 'n/a',
292@@ -154,7 +154,7 @@
293 ERROR: Missing required environment variables:
294 {}
295 Please ensure the novarc file has been sourced.''')
296- sys.exit(template.format('\n '.join(missing_required)))
297+ sys.exit(template.format('\n '.join(sorted(missing_required))))
298
299
300 def bootstrap():
301
302=== modified file 'juju-deployer/test_deploy.py'
303--- juju-deployer/test_deploy.py 2014-07-03 17:25:58 +0000
304+++ juju-deployer/test_deploy.py 2014-07-04 21:48:29 +0000
305@@ -36,11 +36,6 @@
306
307 DEFAULT_ENV = {
308 'OS_USERNAME': 'os-user',
309- 'GLANCE_OS_USERNAME': 'glance-os-user',
310- 'GLANCE_OS_AUTH_URL': 'glance-url',
311- 'GLANCE_OS_REGION_NAME': 'glance-region',
312- 'GLANCE_OS_TENANT_NAME': 'glance-tenant',
313- 'GLANCE_OS_PASSWORD': 'glance-password',
314 'OS_AUTH_URL': 'something.stack',
315 'CI_OAUTH_CONSUMER_KEY': 'key',
316 'CI_OAUTH_TOKEN': 'token',
317@@ -61,6 +56,7 @@
318 'CI_TEMPURL_SIGNING_KEY': None,
319 'CI_KEYRING_URI': None,
320 'CI_KEYSERVER_URL': None,
321+ 'GLANCE_OS_USERNAME': None,
322 'HP_ACCESS_KEY_ID': None,
323 'HP_TENANT_ID': None,
324 }
325@@ -79,6 +75,7 @@
326 self.assertEqual(os.environ['CI_KEYRING_URI'], '')
327 self.assertEqual(
328 os.environ['CI_KEYSERVER_URL'], 'keyserver.ubuntu.com')
329+ self.assertEqual('', os.environ['GLANCE_OS_USERNAME'])
330 # CI_TEMPURL_SIGNING_KEY used for canonistack/prodstack TempURL
331 # generation and `deploy.py` generated a new random HMAC-SHA1
332 # (40 chars) key for each deployment.

Subscribers

People subscribed via source and target branches