Merge lp:~heber013/qakit/adding-juju-bootstrapping into lp:qakit

Proposed by Heber Parrucci
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
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/.canonistack/novarc

Then run:

./juju_bootstrap --cloud_name ^THE_NAME_YOU_LIKE^ --nova_file "$HOME/.canonistack/novarc"

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

To post a comment you must log in.
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

Revision history for this message
Max Brustkern (nuclearbob) wrote :

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.

Revision history for this message
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

Revision history for this message
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.

Revision history for this message
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.

Revision history for this message
Max Brustkern (nuclearbob) wrote :

Looks good! Let's land it!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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

Subscribers

People subscribed via source and target branches