Merge lp:~heber013/qakit/adding-juju-bootstrapping into lp:qakit
- adding-juju-bootstrapping
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Max Brustkern |
Approved revision: | 228 |
Merged at revision: | 228 |
Proposed branch: | lp:~heber013/qakit/adding-juju-bootstrapping |
Merge into: | lp:qakit |
Diff against target: |
456 lines (+421/-0) 7 files modified
qakit/juju/README.rst (+19/-0) qakit/juju/__init__.py (+18/-0) qakit/juju/hosts (+5/-0) qakit/juju/juju_bootstrap (+105/-0) qakit/juju/juju_bootstrap.py (+213/-0) qakit/juju/mk-venv (+60/-0) qakit/juju/requirements.txt (+1/-0) |
To merge this branch: | bzr merge lp:~heber013/qakit/adding-juju-bootstrapping |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Max Brustkern (community) | Approve | ||
Review via email: mp+320869@code.launchpad.net |
Commit message
Adding juju bootstrapping
Description of the change
Automate bootstrapping for cloud, controller and model in the host machine using juju.
To test it, you need the file that contains the credentials for the cloud provider.
Example:
if your credentials file is: $HOME/.
Then run:
./juju_bootstrap --cloud_name ^THE_NAME_YOU_LIKE^ --nova_file "$HOME/
Too see instructions in details refer to README.rst
The script was implemented in part using subprocess because python juju clients have several bugs and limitations. I tested these clients:
- jujuclient
- juju
- 220. By Heber Parrucci
-
Fixing relative paths in scripts
- 221. By Heber Parrucci
-
updating README.rst
- 222. By Heber Parrucci
-
Fixing relative paths when executing the script
- 223. By Heber Parrucci
-
Ensuring the nova key is always created
- 224. By Heber Parrucci
-
Adding check if bootstrap command failed
- 225. By Heber Parrucci
-
Adding proper ip range for scaling stack hosts
Max Brustkern (nuclearbob) wrote : | # |
Heber Parrucci (heber013) wrote : | # |
Thanks for the review!!! All comments addressed. Check replies inline for details.
- 226. By Heber Parrucci
-
Addressing review feedback
- 227. By Heber Parrucci
-
minor indentation change
Max Brustkern (nuclearbob) wrote : | # |
Awesome, thanks for all the changes! One question about the placement of the new --replace argument, but other than that, I think it looks good.
Heber Parrucci (heber013) wrote : | # |
Fixing:
* review comment when parsing replace parameter
* logging
* added logic so if replace parameter is set and the controller is bootstrapped it will delete it first before proceeding.
- 228. By Heber Parrucci
-
Fixing:
* review comment when parsing replace parameter
* logging
* added logic so if replace parameter is set and the controller is bootstrapped it will delete it first before proceeding.
Max Brustkern (nuclearbob) wrote : | # |
Looks good! Let's land it!
Preview Diff
1 | === added directory 'qakit/juju' |
2 | === added file 'qakit/juju/README.rst' |
3 | --- qakit/juju/README.rst 1970-01-01 00:00:00 +0000 |
4 | +++ qakit/juju/README.rst 2017-03-28 17:46:02 +0000 |
5 | @@ -0,0 +1,19 @@ |
6 | +############################# |
7 | +Bootstrapping cloud with juju |
8 | +############################# |
9 | + |
10 | + |
11 | +You need the file that contains the credentials for the cloud provider. |
12 | + |
13 | +Follow this steps: |
14 | + |
15 | +Bootstrap the cloud, controller and model in the machine: |
16 | +$ ./qakit/juju/juju_bootstrap --cloud_name ^THE_NAME_YOU_LIKE^ --nova_file ^NOVA_CREDENTIALS_FILE^ |
17 | +For example if you have canonistack credentials in $HOME/.canonistack/novarc: |
18 | +$ ./qakit/juju/juju_bootstrap --cloud_name canonistack --nova_file "$HOME/.canonistack/novarc" |
19 | +Enter the sudo password if asked (Some commands are executed as root and others do not). |
20 | +Note: You need to provide the full path for the nova file. You can use $HOME but not ~. |
21 | +It will install the needed packages and bootstrap the cloud. |
22 | +The controller name will be the same of the cloud. |
23 | +Use --replace parameter to override any existing cloud/credentials with the same name. |
24 | +By default it will not override anything. |
25 | |
26 | === added file 'qakit/juju/__init__.py' |
27 | --- qakit/juju/__init__.py 1970-01-01 00:00:00 +0000 |
28 | +++ qakit/juju/__init__.py 2017-03-28 17:46:02 +0000 |
29 | @@ -0,0 +1,18 @@ |
30 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
31 | +# |
32 | +# Juju Utils |
33 | +# Copyright (C) 2017 Canonical |
34 | +# |
35 | +# This program is free software: you can redistribute it and/or modify |
36 | +# it under the terms of the GNU General Public License as published by |
37 | +# the Free Software Foundation, either version 3 of the License, or |
38 | +# (at your option) any later version. |
39 | +# |
40 | +# This program is distributed in the hope that it will be useful, |
41 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
42 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
43 | +# GNU General Public License for more details. |
44 | +# |
45 | +# You should have received a copy of the GNU General Public License |
46 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
47 | +# |
48 | |
49 | === added file 'qakit/juju/hosts' |
50 | --- qakit/juju/hosts 1970-01-01 00:00:00 +0000 |
51 | +++ qakit/juju/hosts 2017-03-28 17:46:02 +0000 |
52 | @@ -0,0 +1,5 @@ |
53 | +Host 10.55.32.* 10.55.33.* 10.55.34.* 10.55.35.* 10.55.36.* 10.55.37.* 10.55.38.* 10.55.39.* 10.55.40.* 10.55.41.* 10.55.42.* 10.55.43.* 10.55.44.* 10.55.45.* 10.55.46.* 10.55.47.* 10.55.58.* 10.55.60.* 10.55.61.* 10.55.62.* 10.55.63.* *.canonistack |
54 | + User ubuntu |
55 | + |
56 | +Host 10.42.184.* 10.42.185.* 10.42.186.* 10.42.187.* 10.42.188.* 10.42.189.* 10.42.190.* 10.42.191.* *.scalingstack |
57 | + User ubuntu |
58 | |
59 | === added file 'qakit/juju/juju_bootstrap' |
60 | --- qakit/juju/juju_bootstrap 1970-01-01 00:00:00 +0000 |
61 | +++ qakit/juju/juju_bootstrap 2017-03-28 17:46:02 +0000 |
62 | @@ -0,0 +1,105 @@ |
63 | +#!/bin/bash |
64 | + |
65 | +# |
66 | +# Juju Utils |
67 | +# Copyright (C) 2017 Canonical |
68 | +# |
69 | +# This program is free software: you can redistribute it and/or modify |
70 | +# it under the terms of the GNU General Public License as published by |
71 | +# the Free Software Foundation, either version 3 of the License, or |
72 | +# (at your option) any later version. |
73 | +# |
74 | +# This program is distributed in the hope that it will be useful, |
75 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
76 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
77 | +# GNU General Public License for more details. |
78 | +# |
79 | +# You should have received a copy of the GNU General Public License |
80 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
81 | +# |
82 | + |
83 | +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" |
84 | + |
85 | +sudo apt-get install cloud-utils python-novaclient -y |
86 | + |
87 | + |
88 | +while [[ $# -gt 0 ]] |
89 | + do |
90 | + key="$1" |
91 | + case ${key} in |
92 | + --nova_file) |
93 | + nova_file="$2" |
94 | + shift # past argument |
95 | + ;; |
96 | + --cloud_name) |
97 | + cloud_name="$2" |
98 | + shift # past argument |
99 | + ;; |
100 | + --proxy) |
101 | + proxy="$2" |
102 | + shift # past argument |
103 | + ;; |
104 | + --replace) |
105 | + replace=YES |
106 | + ;; |
107 | + esac |
108 | + shift # past argument or value |
109 | + done |
110 | + |
111 | + |
112 | +function add_nova_key { |
113 | + nova keypair-show ${NOVA_USERNAME}_${OS_REGION_NAME}_${cloud_name} |
114 | + if [ $? != 0 ]; then |
115 | + install -m 600 /dev/null ${NOVA_KEY_DIR}/${NOVA_USERNAME}_${OS_REGION_NAME}_${cloud_name}.key |
116 | + nova keypair-add ${NOVA_USERNAME}_${OS_REGION_NAME}_${cloud_name} > ${NOVA_KEY_DIR}/${NOVA_USERNAME}_${OS_REGION_NAME}_${cloud_name}.key |
117 | + fi |
118 | + } |
119 | + |
120 | +source ${nova_file} |
121 | + |
122 | +# Adding hosts if they are not present |
123 | +hosts=$(cat ${DIR}/hosts) |
124 | +config=$(cat ~/.ssh/config) |
125 | + |
126 | +if [[ ! ${config} == *"$hosts"* ]]; then |
127 | + cat ${DIR}/hosts >> ~/.ssh/config |
128 | +else |
129 | + echo "SSH config already contains hosts" |
130 | +fi |
131 | + |
132 | +# Create nova key |
133 | +add_nova_key |
134 | + |
135 | +# Create python virtual env |
136 | +. ${DIR}/mk-venv -r ${DIR}/requirements.txt --name juju_ve --proxy ${proxy} |
137 | + |
138 | +# Bootstrap juju cloud/controller/model |
139 | +parameters="--cloud_name ${cloud_name} --username ${OS_USERNAME} --password ${OS_PASSWORD} --tenant ${OS_TENANT_NAME} --region ${OS_REGION_NAME} --url ${OS_AUTH_URL}" |
140 | +if [ ! -z ${replace} ]; then |
141 | + parameters="$parameters --replace" |
142 | +fi |
143 | +${DIR}/juju_ve/bin/python3 ${DIR}/juju_bootstrap.py ${parameters} |
144 | +bootstrap_result=$? |
145 | +if [ ${bootstrap_result} != 0 ]; then |
146 | + echo "Juju Bootstrap failed. Exiting..." |
147 | + exit 1 |
148 | +fi |
149 | + |
150 | +# Enable ssh |
151 | +my_temp=$(mktemp) |
152 | +juju add-ssh-key "$(cat ~/.ssh/id_rsa.pub)" &> ${my_temp} |
153 | +if [ -s my_temp ]; then |
154 | + grep -q "duplicate ssh key" ${my_temp} |
155 | + if [ $? -eq 0 ]; then |
156 | + echo "ssh key already exists in the host" |
157 | + else |
158 | + echo "Adding public ssh key failed. Exiting..." |
159 | + exit 1 |
160 | + fi |
161 | +fi |
162 | + |
163 | +rm -f my_temp |
164 | + |
165 | +deactivate |
166 | + |
167 | +echo "Juju Bootstrap successful!!!" |
168 | |
169 | === added file 'qakit/juju/juju_bootstrap.py' |
170 | --- qakit/juju/juju_bootstrap.py 1970-01-01 00:00:00 +0000 |
171 | +++ qakit/juju/juju_bootstrap.py 2017-03-28 17:46:02 +0000 |
172 | @@ -0,0 +1,213 @@ |
173 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
174 | + |
175 | +# |
176 | +# Juju Utils |
177 | +# Copyright (C) 2017 Canonical |
178 | +# |
179 | +# This program is free software: you can redistribute it and/or modify |
180 | +# it under the terms of the GNU General Public License as published by |
181 | +# the Free Software Foundation, either version 3 of the License, or |
182 | +# (at your option) any later version. |
183 | +# |
184 | +# This program is distributed in the hope that it will be useful, |
185 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
186 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
187 | +# GNU General Public License for more details. |
188 | +# |
189 | +# You should have received a copy of the GNU General Public License |
190 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
191 | +# |
192 | + |
193 | + |
194 | +"""Bootstrap a cloud using juju""" |
195 | + |
196 | +import argparse |
197 | +import subprocess |
198 | +import tempfile |
199 | + |
200 | +import logging |
201 | +import yaml |
202 | + |
203 | +PACKAGES = ['juju', 'lxd', 'zfsutils-linux'] |
204 | +PPAS = ['ppa:juju/stable'] |
205 | + |
206 | +logging.basicConfig() |
207 | +LOGGER = logging.getLogger('juju_bootstrap') |
208 | +LOGGER.setLevel(logging.DEBUG) |
209 | +LOGGER.propagate = True |
210 | + |
211 | + |
212 | +def is_installed(pgk_name): |
213 | + """ |
214 | + Check if package is installed |
215 | + :param pgk_name: package name |
216 | + :return: True if package is installed, False otherwise |
217 | + """ |
218 | + try: |
219 | + subprocess.check_call(['dpkg', '-s', pgk_name]) |
220 | + return True |
221 | + except subprocess.CalledProcessError: |
222 | + return False |
223 | + |
224 | + |
225 | +def install_packages(packages): |
226 | + """Install the given packages""" |
227 | + subprocess.check_call(['sudo', 'apt', 'update']) |
228 | + for package in packages: |
229 | + if not is_installed(package): |
230 | + subprocess.check_call(['sudo', 'apt', 'install', package, '-y']) |
231 | + |
232 | + |
233 | +def add_ppas(ppas): |
234 | + """Add the given ppas""" |
235 | + for ppa in ppas: |
236 | + subprocess.check_call(['sudo', 'add-apt-repository', ppa, '-y']) |
237 | + subprocess.check_call(['sudo', 'apt', 'update']) |
238 | + |
239 | + |
240 | +def build_cloud_yaml(name, region, url): |
241 | + """Build cloud into yaml file |
242 | + |
243 | + :param name: the cloud name |
244 | + :param region: the region name |
245 | + :param url: the auth url |
246 | + :return: the cloud yaml file |
247 | + """ |
248 | + cloud = {"auth-types": ["access-key", "userpass"], |
249 | + 'regions': {region: {'endpoint': url}}, "type": "openstack"} |
250 | + clouds_dict = {'clouds': {name: cloud}} |
251 | + cloud_yaml = tempfile.NamedTemporaryFile() |
252 | + with open(cloud_yaml.name, 'w') as temp: |
253 | + temp.write(yaml.dump(clouds_dict)) |
254 | + return cloud_yaml |
255 | + |
256 | + |
257 | +def build_credentials(cloud, username, password, tenant, auth_type='userpass'): |
258 | + """Build credentials into yaml file |
259 | + |
260 | + :param cloud: the cloud name |
261 | + :param username: the user name |
262 | + :param password: the password |
263 | + :param tenant: the tenant name |
264 | + :param auth_type: the auth-type |
265 | + :return: the credentials yaml file |
266 | + """ |
267 | + credentials_dict = { |
268 | + 'credentials': { |
269 | + cloud: { |
270 | + cloud: |
271 | + {'auth-type': auth_type, |
272 | + 'username': username, |
273 | + 'password': password, |
274 | + 'tenant-name': tenant, |
275 | + } |
276 | + } |
277 | + } |
278 | + } |
279 | + credentials_yaml = tempfile.NamedTemporaryFile() |
280 | + with open(credentials_yaml.name, 'w') as temp: |
281 | + temp.write(yaml.dump(credentials_dict)) |
282 | + return credentials_yaml |
283 | + |
284 | + |
285 | +def add_cloud(name, region, url, replace): |
286 | + """Add cloud with juju |
287 | + |
288 | + :param name: the cloud name |
289 | + :param region: the region name |
290 | + :param url: the auth url |
291 | + :param replace: whether to replace the cloud if exists |
292 | + """ |
293 | + LOGGER.info('Trying to add cloud %s', name) |
294 | + cloud_yaml = build_cloud_yaml(name, region, url) |
295 | + command = ['juju', 'add-cloud', name, cloud_yaml.name] |
296 | + if replace: |
297 | + command.append('--replace') |
298 | + subprocess.check_call(command) |
299 | + LOGGER.info('Cloud %s added successfully', name) |
300 | + |
301 | + |
302 | +def add_credentials(cloud_name, username, password, tenant, replace): |
303 | + """Add credentials for the cloud |
304 | + |
305 | + :param cloud_name: the cloud name |
306 | + :param username: the user name |
307 | + :param password: the password |
308 | + :param tenant: the tenant name |
309 | + :param replace: whether to replace the credentials if exists |
310 | + """ |
311 | + LOGGER.info('Trying to add credentials for cloud %s', cloud_name) |
312 | + credentials_yaml = build_credentials(cloud_name, username, password, tenant) |
313 | + command = ['juju', 'add-credential', cloud_name, '-f', credentials_yaml.name] |
314 | + if replace: |
315 | + command.append('--replace') |
316 | + subprocess.check_call(command) |
317 | + LOGGER.info('Credentials added successfully for cloud %s', cloud_name) |
318 | + |
319 | + |
320 | +def is_controller_bootstrapped(name): |
321 | + """Check if controller is already bootstrapped |
322 | + |
323 | + :param name: the controller name to check |
324 | + :return: True if controller is bootstrapped, False otherwise |
325 | + """ |
326 | + try: |
327 | + LOGGER.info('Checking if controller %s is bootstrapped ', name) |
328 | + subprocess.check_call(['juju', 'show-controller', name]) |
329 | + LOGGER.info('Controller %s is already bootstrapped ', name) |
330 | + return True |
331 | + except subprocess.CalledProcessError: |
332 | + LOGGER.info('Controller %s is not bootstrapped ', name) |
333 | + return False |
334 | + |
335 | + |
336 | +def bootstrap(cloud, username, password, tenant, region, url, replace): |
337 | + """Bootstrap a cloud using juju |
338 | + |
339 | + :param cloud: the cloud name |
340 | + :param username: the username |
341 | + :param password: the password |
342 | + :param tenant: the tenant name |
343 | + :param region: the region name |
344 | + :param url: the auth url |
345 | + :param replace: whether to replace the cloud and |
346 | + credentials if exists |
347 | + """ |
348 | + bootstrapped = is_controller_bootstrapped(cloud) |
349 | + if replace and bootstrapped: |
350 | + LOGGER.info('Controller %s already bootstrapped. ' |
351 | + 'Destroying it before proceeding...', cloud) |
352 | + subprocess.check_call(['juju', 'destroy-controller', cloud, |
353 | + '--destroy-all-models', '-y']) |
354 | + elif not replace and bootstrapped: |
355 | + LOGGER.info('Controller %s already bootstrapped and ' |
356 | + 'replace parameter is not set...Exiting', cloud) |
357 | + return |
358 | + LOGGER.info('About to bootstrap controller %s...', cloud) |
359 | + add_cloud(cloud, region, url, replace) |
360 | + add_credentials(cloud, username, password, tenant, replace) |
361 | + subprocess.check_call(['juju', |
362 | + 'bootstrap', |
363 | + cloud, |
364 | + cloud, |
365 | + '--credential', |
366 | + cloud]) |
367 | + |
368 | + |
369 | +if __name__ == '__main__': |
370 | + PARSER = argparse.ArgumentParser(description= |
371 | + 'Bootstrap a juju cloud') |
372 | + PARSER.add_argument('--cloud_name', |
373 | + help='Cloud name that will be also use ' |
374 | + 'for controller name') |
375 | + PARSER.add_argument('--username') |
376 | + PARSER.add_argument('--password') |
377 | + PARSER.add_argument('--tenant') |
378 | + PARSER.add_argument('--region') |
379 | + PARSER.add_argument('--url') |
380 | + PARSER.add_argument('--replace', action='store_true') |
381 | + ARGS = PARSER.parse_args() |
382 | + add_ppas(PPAS) |
383 | + install_packages(PACKAGES) |
384 | + bootstrap(ARGS.cloud_name, ARGS.username, ARGS.password, ARGS.tenant, |
385 | + ARGS.region, ARGS.url, ARGS.replace) |
386 | |
387 | === added file 'qakit/juju/mk-venv' |
388 | --- qakit/juju/mk-venv 1970-01-01 00:00:00 +0000 |
389 | +++ qakit/juju/mk-venv 2017-03-28 17:46:02 +0000 |
390 | @@ -0,0 +1,60 @@ |
391 | +#!/bin/bash |
392 | + |
393 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
394 | +# |
395 | +# Juju Utils |
396 | +# Copyright (C) 2017 Canonical |
397 | +# |
398 | +# This program is free software: you can redistribute it and/or modify |
399 | +# it under the terms of the GNU General Public License as published by |
400 | +# the Free Software Foundation, either version 3 of the License, or |
401 | +# (at your option) any later version. |
402 | +# |
403 | +# This program is distributed in the hope that it will be useful, |
404 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
405 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
406 | +# GNU General Public License for more details. |
407 | +# |
408 | +# You should have received a copy of the GNU General Public License |
409 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
410 | +# |
411 | + |
412 | +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" |
413 | + |
414 | +while [[ $# -gt 1 ]] |
415 | + do |
416 | + key="$1" |
417 | + case ${key} in |
418 | + --proxy) |
419 | + proxy="$2" |
420 | + shift # past argument |
421 | + ;; |
422 | + -r|--requirements) |
423 | + requirements="$2" |
424 | + shift # past argument |
425 | + ;; |
426 | + --name) |
427 | + name="$2" |
428 | + shift # past argument |
429 | + ;; |
430 | + esac |
431 | + shift # past argument or value |
432 | + done |
433 | + |
434 | +if [ -z "$name" ]; then |
435 | + name=ve |
436 | +fi |
437 | + |
438 | +if [ -z "$requirements" ]; then |
439 | + requirements=requirements.txt |
440 | +fi |
441 | + |
442 | +virtualenv --python=python3 ${DIR}/${name} |
443 | +. ${DIR}/${name}/bin/activate |
444 | + |
445 | +if [ ! -z "$proxy" ]; then |
446 | + echo "Using proxy: ${proxy} to install dependencies" |
447 | + pip3 install --proxy ${proxy} -r ${requirements} |
448 | +else |
449 | + pip3 install -r ${requirements} |
450 | +fi |
451 | |
452 | === added file 'qakit/juju/requirements.txt' |
453 | --- qakit/juju/requirements.txt 1970-01-01 00:00:00 +0000 |
454 | +++ qakit/juju/requirements.txt 2017-03-28 17:46:02 +0000 |
455 | @@ -0,0 +1,1 @@ |
456 | +pyyaml |
I've got a variety of diff comments and questions, many of which are concerned with the potential to duplicate config options or interfere with existing setups.