Merge lp:~ibmcharmers/charms/trusty/ibm-mq/trunk into lp:charms/trusty/ibm-mq

Proposed by Anita Nayak
Status: Needs review
Proposed branch: lp:~ibmcharmers/charms/trusty/ibm-mq/trunk
Merge into: lp:charms/trusty/ibm-mq
Diff against target: 4832 lines (+3632/-478)
83 files modified
Makefile (+24/-0)
README.md (+114/-127)
bin/layer_option (+24/-0)
config.yaml (+0/-12)
copyright (+1/-1)
deps/layer/layer-apt/.gitignore (+2/-0)
deps/layer/layer-apt/README.md (+195/-0)
deps/layer/layer-apt/config.yaml (+34/-0)
deps/layer/layer-apt/copyright (+15/-0)
deps/layer/layer-apt/layer.yaml (+5/-0)
deps/layer/layer-apt/lib/charms/__init__.py (+2/-0)
deps/layer/layer-apt/lib/charms/apt.py (+182/-0)
deps/layer/layer-apt/reactive/apt.py (+131/-0)
deps/layer/layer-basic/.gitignore (+5/-0)
deps/layer/layer-basic/Makefile (+24/-0)
deps/layer/layer-basic/README.md (+221/-0)
deps/layer/layer-basic/bin/layer_option (+24/-0)
deps/layer/layer-basic/copyright (+9/-0)
deps/layer/layer-basic/hooks/config-changed (+19/-0)
deps/layer/layer-basic/hooks/hook.template (+19/-0)
deps/layer/layer-basic/hooks/install (+19/-0)
deps/layer/layer-basic/hooks/leader-elected (+19/-0)
deps/layer/layer-basic/hooks/leader-settings-changed (+19/-0)
deps/layer/layer-basic/hooks/start (+19/-0)
deps/layer/layer-basic/hooks/stop (+19/-0)
deps/layer/layer-basic/hooks/update-status (+19/-0)
deps/layer/layer-basic/hooks/upgrade-charm (+28/-0)
deps/layer/layer-basic/layer.yaml (+18/-0)
deps/layer/layer-basic/lib/charms/layer/__init__.py (+21/-0)
deps/layer/layer-basic/lib/charms/layer/basic.py (+159/-0)
deps/layer/layer-basic/lib/charms/layer/execd.py (+138/-0)
deps/layer/layer-basic/metadata.yaml (+1/-0)
deps/layer/layer-basic/requirements.txt (+2/-0)
deps/layer/layer-basic/tox.ini (+12/-0)
deps/layer/layer-basic/wheelhouse.txt (+3/-0)
deps/layer/layer-leadership/.gitignore (+2/-0)
deps/layer/layer-leadership/README.md (+88/-0)
deps/layer/layer-leadership/copyright (+15/-0)
deps/layer/layer-leadership/layer.yaml (+17/-0)
deps/layer/layer-leadership/lib/charms/__init__.py (+2/-0)
deps/layer/layer-leadership/lib/charms/leadership.py (+58/-0)
deps/layer/layer-leadership/reactive/leadership.py (+68/-0)
deps/layer/trunk/README.md (+57/-0)
deps/layer/trunk/config.yaml (+29/-0)
deps/layer/trunk/layer.yaml (+9/-0)
deps/layer/trunk/metadata.yaml (+15/-0)
deps/layer/trunk/reactive/ibm-base.sh (+107/-0)
hooks/config-changed (+0/-242)
hooks/hook.template (+19/-0)
hooks/install (+0/-30)
hooks/leader-elected (+19/-0)
hooks/leader-settings-changed (+19/-0)
hooks/messaging-relation-broken (+19/-0)
hooks/messaging-relation-changed (+19/-0)
hooks/messaging-relation-departed (+19/-0)
hooks/messaging-relation-joined (+0/-12)
hooks/relations/ibm-mq/README.md (+42/-0)
hooks/relations/ibm-mq/interface.yaml (+4/-0)
hooks/relations/ibm-mq/provides.py (+50/-0)
hooks/relations/ibm-mq/requires.py (+42/-0)
hooks/start (+0/-8)
hooks/stop (+0/-6)
hooks/update-status (+19/-0)
hooks/upgrade-charm (+28/-0)
layer.yaml (+21/-0)
lib/charms/__init__.py (+2/-0)
lib/charms/apt.py (+182/-0)
lib/charms/layer/__init__.py (+21/-0)
lib/charms/layer/basic.py (+159/-0)
lib/charms/layer/execd.py (+138/-0)
lib/charms/leadership.py (+58/-0)
metadata.yaml (+19/-14)
reactive/apt.py (+131/-0)
reactive/ibm-base.sh (+107/-0)
reactive/ibm-mq.sh (+340/-0)
reactive/leadership.py (+68/-0)
reactive/test (+1/-0)
requirements.txt (+2/-0)
revision (+1/-1)
tests/00-setup (+13/-1)
tests/10-bundles-test.py (+45/-14)
tests/bundles.yaml (+0/-10)
tox.ini (+12/-0)
To merge this branch: bzr merge lp:~ibmcharmers/charms/trusty/ibm-mq/trunk
Reviewer Review Type Date Requested Status
Matt Bruzek Pending
Review via email: mp+307673@code.launchpad.net

Description of the change

Hi Matt,

I have updated ibm-mq charm as per below:

1. updated /etc/pam.d/common-session file to reflect the config changes for soft and hard nofile limits.
2. Updated the README.md appropriately.

To post a comment you must log in.
53. By Anita Nayak

checkin after review comment incorporation to interface interface-ibm-mq

Unmerged revisions

53. By Anita Nayak

checkin after review comment incorporation to interface interface-ibm-mq

52. By Anita Nayak

Check in for trusty version of ibm-mq

51. By Anita Nayak

check-in with proper file permission

50. By Anita Nayak

Check-in for ibm-mq layered for trusty

49. By krishna bandi

updated ibm-mq charm

48. By krishna bandi

Updated Amulet test files

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'Makefile'
2--- Makefile 1970-01-01 00:00:00 +0000
3+++ Makefile 2017-02-15 08:51:47 +0000
4@@ -0,0 +1,24 @@
5+#!/usr/bin/make
6+
7+all: lint unit_test
8+
9+
10+.PHONY: clean
11+clean:
12+ @rm -rf .tox
13+
14+.PHONY: apt_prereqs
15+apt_prereqs:
16+ @# Need tox, but don't install the apt version unless we have to (don't want to conflict with pip)
17+ @which tox >/dev/null || (sudo apt-get install -y python-pip && sudo pip install tox)
18+
19+.PHONY: lint
20+lint: apt_prereqs
21+ @tox --notest
22+ @PATH=.tox/py34/bin:.tox/py35/bin flake8 $(wildcard hooks reactive lib unit_tests tests)
23+ @charm proof
24+
25+.PHONY: unit_test
26+unit_test: apt_prereqs
27+ @echo Starting tests...
28+ tox
29
30=== modified file 'README.md'
31--- README.md 2015-07-09 18:38:01 +0000
32+++ README.md 2017-02-15 08:51:47 +0000
33@@ -1,157 +1,144 @@
34-
35-Charm for IBM MQ
36-
37-Overview
38---------
39+## Charm for IBM MQ
40+
41+### Overview
42
43 IBM MQ
44
45-IBM MQ provides for messaging services to transport multiple types of data.
46-MQ Runtime, Server and Sample components from IBM MQ product
47-can be deployed using this charm.
48+IBM MQ provides messaging services to transport multiple types of data.
49+MQ Runtime, Server and Sample components from IBM MQ product can be deployed using this charm.
50
51
52 This charm installs IBM MQ product and configures it's IP Address and default
53 port number (1414). MQ Administrators can setup queue managers
54 and queues. This will allow IBM MQ client application to transmit/receive messages.
55
56-More information on IBM MQ available at the [Product Page][product-page]
57-and [IBM Knowledge Center][mq-v8-info]
58-
59-Usage
60------
61-
62-Download (see below) your licensed IBM MQ 8.0.0.2 software and copy it to the
63-`files/archives` directory of MQ charm.
64-
65-For e.g:
66-
67- * Go to the `files/archives` directory.
68-
69- * Copy the `WS_MQ_8.0.0.2_FOR_LIN_ON_LE_POWER.tar.gz` file to this directory for
70- `POWER LE` base machines. For `x86_64`, use `WS_MQ_V8.0.0.2_LINUX_ON_X86_64_I.tar.gz`.
71-
72- * The file name you have could be different from the example above (but with the
73- `.gz` extension). Ensure a backup copy of the file.
74-
75-To install the downloaded binaries you must agree to the IBM license. The license
76-file(s) can be found under the `files/archives/licenses` directory on extraction
77-of IBM MQ software using the following command:
78-
79- tar xvfz *.gz --strip-components=1
80-
81-The license can also be viewed by running the following command after extracting
82-IBM MQ to the files/archives directory:
83-
84- ./mqlicense.sh -text_only
85-
86-Run the following to deploy this charm:
87-
88+More information on IBM MQ available at the [Product Page](http://www-03.ibm.com/software/products/en/ibm-mq) and [IBM Knowledge Center](http://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.helphome.v80.doc/WelcomePagev8r0.htm)
89+
90+### Usage
91+
92+Download your licensed IBM MQ software and host it in a way that can be downloaded using curl.
93+curl-url and curl-opts are two main options we are using from ibm-base-layer to download the ibm product as IBM-MQ is built on top of the ibm-base layer [More Info](http://interfaces.juju.solutions/layer/ibm-base/)
94+
95+### Deploy
96+
97+Run the following commands to deploy this charm:
98+
99 juju deploy ibm-mq
100
101-At this point the charm will wait until the user accepts the license. If you
102-agree to the license, run the following command:
103-
104- juju set ibm-mq accept-ibm-mq-license=True
105+Here the charm will wait until the user accepts the license. If you agree to the license, run the following command.
106+
107+ juju set-config ibm-mq license_accepted="True"
108+
109+IBM MQ charm also needs an host from where the packages can be downloaded (the user has to keep the MQ packages on this host server), from the directory where the package is kept, user name and password to connect to the host server (as mentioned in the creating repositories section).
110+
111+We have to provide values for curl-url and curl-opt to download the product.
112+curl-url with host name,package_dir, package name, checksum value, curl-opts with user name and password, to proceed with the installation. To provide these run the following command:
113+
114+ juju set-config ibm-mq curl_url="<hostname>/<package_dir><package_name>?<sha512=value>"
115+ juju set-config ibm-mq curl_opts="-u <username>:<password>"
116+
117+
118+For eg:
119+ juju set-config ibm-mq curl_url="http://1.2.3.4/debs/MQ/WS_MQ_V8.0.0.2_LINUX_ON_X86_64_I.tar.gz?sha512=cd604a577ae51d93fc384241ce2faf21927b202d0a9e5f9b9a25caf4460fd6c34f32649caef8ecd543c244413788fe105987d16553e2d4754ef02d924843e95f"
120+
121+ juju set-config ibm-mq curl_opts="-u root:root123"
122+
123+Setting the license-accepted to False will uninstall the product:
124+
125+ juju set-config ibm-mq license_accepted="False"
126+
127+
128+### Update the following system configuration manually:
129
130 In order to create MQ objects like queue, it is required that some
131-of the system configurations be updated. Details provided at
132-[IBM Knowledge Center][mq-config-info].
133-
134-The Charm updates the following system configuration:
135+ of the system configurations be updated. Details provided at
136+ [IBM Knowledge Center][mq-config-info].
137
138 * In `/etc/sysctl.conf`:
139
140 fs.file-max = 524288
141 kernel.shmmax = 268435456
142
143- * In `/etc/security/limits.conf`:
144+### Relation with other services:
145+
146+ ibm-mq can have relation with other services to get the service specific queue manager. Per example if ibm-mq gets relation with ibm-was then, ibm-was specific queue manger will be created like 'ibm-wasQM'.
147+
148+ juju add-relation ibm-mq:messaging ibm-was-base:wasmessaging
149+ or
150+
151+ juju add-relation ibm-mq ibm-was-base
152+
153+IBM-MQ sets Queue Manager name, Queue Name, IP Address and Port for the connected services.
154+ It creates a Queue Manager for the consumer application and a local queue i.e QUEUE1 for above Queue Manager.
155+ But as a Queue Name it sets a filename where MQSC Commands can be edited for further operation.
156+
157+on removal of relation with the service with which already relation was established, the service specific queue manger will be removed i.e in the above case 'ibm-wasQM' will be removed.
158+
159+ juju remove-relation ibm-mq:messaging ibm-was-base:wasmessaging
160+or
161+
162+ juju remove-relation ibm-mq ibm-was-base
163+##### The Charm updates the following system configuration:
164+
165+In order to create MQ objects like queue, it is required that some
166+of the system configurations be updated. Details provided at
167+[IBM Knowledge Center][mq-config-info].
168+
169+ * Adds user `ubuntu` to group `mqm`.
170+
171+ * In `/etc/security/limits.conf` if hard nofile and soft nofile limit for user `mqm` are less than 10240:
172
173 mqm hard nofile 10240
174 mqm soft nofile 10240
175-
176- * Adds user `ubuntu` to group `mqm`.
177+
178+ * Updates `/etc/pam.d/common-session` file to take the above configurations to take effect
179
180 Verify these changes have been made successfully if MQ throws up any errors.
181
182-Configuration
183--------------
184-
185-`accept-ibm-mq-license`
186-Before you can use or install IBM MQ,
187-you must accept the terms of International License
188-Agreement for Non-Warranted Programs and additional license information.
189-Please read the license agreement carefully.
190-
191-The license file(s) can be found under the `files/archives` directory after
192-downloading and extracting IBM MQ there. The license can also be viewed by
193-running the following command after extracting IBM MQ to the `files/archives`
194-directory:
195- ./mqlicense.sh -text_only
196-
197-Set the value of accept-ibm-mq-license to True if you agree to IBM MQ license.
198-
199-**The IBM MQ software cannot be installed until the terms and
200-conditions are accepted. The charm will not function correctly until the
201-this configuration option is set to True.**
202-
203-Known Limitations
204------------------
205+
206+#### Configuration
207+
208+**license_accepted** (boolean) Some IBM charms require acceptance of a license before installation can proceed. If required, setting this option to True indicates that you have read and accepted the IBM terms and conditions found in the license file referenced by the charm.
209+
210+ **curl_url**
211+
212+ For downloading package we need to set curl_url with following options.
213+ - *hostname* - The web server host name from which IBM MQ installation packages can be downloaded.
214+ - *package_dir* - The package directory path in the web server.
215+ - *package name* - The IBM MQ Package name.
216+ - *checksum value* - Checksum value to check integrity of IBM MQ package. The Charm
217+ uses sha512sum to check the integrity. If empty, it does not carry out the
218+ integrity check. Use `sha512sum <Package Location/Package Name>`
219+ to find out Checksum value for downloaded package.
220+
221+**curl_opts**
222+
223+ We need to set curl_opts with following options. - username - User name of the webserver/sftp host. - password - Password of the webserver/sftp host.
224+
225+##### Known Limitations:
226
227 Local providers does not allow updates to system configuration using sysctl
228 command. These configurations will need be done manually for MQ to function.
229 The required configurations are provided in this document.
230
231-IBM MQ Information
232--------------------
233-
234-### (1) General Information
235-
236-Details about MQ available [here][mq-info].
237-
238-Details about MQ Version 8.0 available at [IBM Knowledge Center][mq-v8-info].
239-
240-### (2) Download Information
241-
242-Information on procuring MQ product is available at the [Product Page][product-page]
243-and at the [Passport Advantage Site][passport].
244-
245-A development version for `x86_64` is available [here][dev-download].
246-After clicking on the above link and if you agree to the license, click on
247-'I agree' link to download the IBM MQ package.
248-
249-A 90 days trial version is available [here][trial-download].
250-
251-### (3) License Information
252-
253-License information for IBM MQ is available as part of the downloaded product
254-package. The license can be viewed by running the following command after
255-extracting IBM MQ:
256-
257- ./mqlicense.sh -text_only
258-
259-License for the development version is available
260-[here][license].
261-
262-# Contact Information
263-
264-For issues with this charm, please contact <jujusupp@us.ibm.com>
265-
266-
267-<!-- Links -->
268-
269-[mq-v8-info]: http://www-01.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.helphome.v80.doc/WelcomePagev8r0.htm "MQ Version 8.0"
270-
271-[product-page]: http://www-03.ibm.com/software/products/en/ibm-mq "MQ Product Page"
272-
273-[mq-config-info]: http://www-01.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.ins.doc/q008550_.htm?lang=en
274-
275-[mq-info]: http://www-01.ibm.com/software/integration/wmq/library/ "MQ Info"
276-
277-[passport]: http://www-01.ibm.com/software/passportadvantage/ "Passport Advantage"
278-
279-[dev-download]: http://www14.software.ibm.com/cgi-bin/weblap/lap.pl?popup=Y&li_formnum=L-APIG-9BUHAE&accepted_url=http://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messaging/mqadv/mqadv_dev80_linux_x86-64.tar.gz "x86_64 Dev Download"
280-
281-[trial-download]: http://www-03.ibm.com/software/products/en/ibm-mq "Trial Download"
282-
283-[license]: http://www14.software.ibm.com/cgi-bin/weblap/lap.pl?popup=Y&li_formnum=L-APIG-9BUHAE&accepted_url=http://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messaging/mqadv/mqadv_dev80_linux_x86-64.tar.gz "License"
284+### IBM MQ Information
285+
286+(1) General Information
287+Details about MQ available [here](http://www-01.ibm.com/software/integration/wmq/library/).
288+Details about MQ Version 8.0 available at [IBM Knowledge Center](http://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.helphome.v80.doc/WelcomePagev8r0.htm).
289+
290+(2) Download Information
291+Information on procuring MQ product is available at the [Product Page](http://www-03.ibm.com/software/products/en/ibm-mq) and at the [Passport Advantage Site](http://www-01.ibm.com/software/passportadvantage/).
292+
293+A development version for x86_64 is available [here](http://www14.software.ibm.com/cgi-bin/weblap/lap.pl?popup=Y&li_formnum=L-APIG-9BUHAE&accepted_url=http://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messaging/mqadv/mqadv_dev80_linux_x86-64.tar.gz). After clicking on the above link and if you agree to the license, click on 'I agree' link to download the IBM MQ package.
294+A 90 days trial version is available [here](http://www-03.ibm.com/software/products/en/ibm-mq).
295+
296+(3) License Information
297+License information for IBM MQ is available as part of the downloaded product package. The license can be viewed by running the following command after extracting IBM MQ.
298+
299+./mqlicense.sh -text_only
300+
301+License for the development version is available [here](http://www14.software.ibm.com/cgi-bin/weblap/lap.pl?popup=Y&li_formnum=L-APIG-9BUHAE&accepted_url=http://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messaging/mqadv/mqadv_dev80_linux_x86-64.tar.gz).
302+
303+Contact Information
304+For issues with this charm, please contact jujusupp@us.ibm.com
305
306=== added directory 'bin'
307=== added file 'bin/layer_option'
308--- bin/layer_option 1970-01-01 00:00:00 +0000
309+++ bin/layer_option 2017-02-15 08:51:47 +0000
310@@ -0,0 +1,24 @@
311+#!/usr/bin/env python3
312+
313+import sys
314+sys.path.append('lib')
315+
316+import argparse
317+from charms.layer import options
318+
319+
320+parser = argparse.ArgumentParser(description='Access layer options.')
321+parser.add_argument('section',
322+ help='the section, or layer, the option is from')
323+parser.add_argument('option',
324+ help='the option to access')
325+
326+args = parser.parse_args()
327+value = options(args.section).get(args.option, '')
328+if isinstance(value, bool):
329+ sys.exit(0 if value else 1)
330+elif isinstance(value, list):
331+ for val in value:
332+ print(val)
333+else:
334+ print(value)
335
336=== added file 'config.yaml'
337--- config.yaml 1970-01-01 00:00:00 +0000
338+++ config.yaml 2017-02-15 08:51:47 +0000
339@@ -0,0 +1,55 @@
340+"options":
341+ "extra_packages":
342+ "description": "Space separated list of extra deb packages to install.\n"
343+ "type": "string"
344+ "default": ""
345+ "package_status":
346+ "default": "install"
347+ "type": "string"
348+ "description": "The status of service-affecting packages will be set to this value\
349+ \ in the dpkg database. Valid values are \"install\" and \"hold\".\n"
350+ "install_sources":
351+ "description": "List of extra apt sources, per charm-helpers standard format (a\
352+ \ yaml list of strings encoded as a string). Each source may be either a line\
353+ \ that can be added directly to sources.list(5), or in the form ppa:<user>/<ppa-name>\
354+ \ for adding Personal Package Archives, or a distribution component to enable.\n"
355+ "type": "string"
356+ "default": ""
357+ "install_keys":
358+ "description": "List of signing keys for install_sources package sources, per\
359+ \ charmhelpers standard format (a yaml list of strings encoded as a string).\
360+ \ The keys should be the full ASCII armoured GPG public keys. While GPG key\
361+ \ ids are also supported and looked up on a keyserver, operators should be aware\
362+ \ that this mechanism is insecure. null can be used if a standard package signing\
363+ \ key is used that will already be installed on the machine, and for PPA sources\
364+ \ where the package signing key is securely retrieved from Launchpad.\n"
365+ "type": "string"
366+ "default": ""
367+ "curl_url":
368+ "type": "string"
369+ "default": ""
370+ "description": |
371+ Location of the IBM product installation file(s). This should be a URL
372+ that curl can use to download files. Multiple URLs should be separated
373+ by a space. NOTE: cryptographic verification is required and must be
374+ specified as part of the URL query string with the key a valid hash
375+ algorithms md5, sha256, or sha512, and the the checksum value itself
376+ (http://<url>?[md5|sha256|sha512]=<checksum>).
377+ For example:
378+ 'http://example.com/file.tgz?sha256=<sum>'
379+ 'sftp://example.com/file1.tgz?md5=<sum> ftp://example.com/file2.tgz?md5=<sum>'
380+ "curl_opts":
381+ "type": "string"
382+ "default": ""
383+ "description": |
384+ The options passed to the 'curl' command when fetching files from
385+ curl_url. For example:
386+ '-u <user:password>'
387+ "license_accepted":
388+ "type": "boolean"
389+ "default": !!bool "false"
390+ "description": |
391+ Some IBM charms require acceptance of a license before installation
392+ can proceed. If required, setting this option to True indicates that you
393+ have read and accepted the IBM terms and conditions found in the license
394+ file referenced by the charm.
395
396=== removed file 'config.yaml'
397--- config.yaml 2015-05-03 16:45:29 +0000
398+++ config.yaml 1970-01-01 00:00:00 +0000
399@@ -1,12 +0,0 @@
400-options:
401- accept-ibm-mq-license:
402- type: boolean
403- default: False
404- description: |
405- The MQ software can only be used by
406- accepting the terms and conditions for IBM MQ License.
407- Setting this option to True
408- indicates that you have read and accepted the IBM MQ terms and
409- conditions found in the license file. The details about accessing
410- the license(s) can be found in the README.
411- ** Do not set to False if a relation exists with this charm **
412
413=== modified file 'copyright'
414--- copyright 2015-03-11 19:52:30 +0000
415+++ copyright 2017-02-15 08:51:47 +0000
416@@ -1,4 +1,4 @@
417-Copyright 2015 IBM Corporation
418+Copyright 2016 IBM Corporation
419
420 This Charm is licensed under the Apache License, Version 2.0 (the "License");
421 you may not use this file except in compliance with the License.
422
423=== added directory 'deps'
424=== added directory 'deps/layer'
425=== added directory 'deps/layer/layer-apt'
426=== added file 'deps/layer/layer-apt/.gitignore'
427--- deps/layer/layer-apt/.gitignore 1970-01-01 00:00:00 +0000
428+++ deps/layer/layer-apt/.gitignore 2017-02-15 08:51:47 +0000
429@@ -0,0 +1,2 @@
430+*~
431+*.pyc
432
433=== added file 'deps/layer/layer-apt/README.md'
434--- deps/layer/layer-apt/README.md 1970-01-01 00:00:00 +0000
435+++ deps/layer/layer-apt/README.md 2017-02-15 08:51:47 +0000
436@@ -0,0 +1,195 @@
437+# Apt layer
438+
439+The Apt layer for Juju enables layered charms to more easily deal with
440+deb packages and apt sources in a simple and efficient manner. It
441+provides consistent configuration for operators, allowing them to
442+easily specify custom apt sources and additional debs required for
443+their particular installations.
444+
445+## Configuration
446+
447+The charm may provide defaults for these service configuration
448+(config.yaml) options, and the operator may override them as required.
449+
450+* `extra_packages`
451+
452+ A space separated list of additional deb packages to install on
453+ each unit.
454+
455+* `package_status`
456+
457+ 'install' or 'hold'. When set to hold, packages installed using
458+ the Apt layer API will be pinned, so that they will not be
459+ automatically upgraded when package updates are performed. 'hold'
460+ is particularly useful for allowing a service such as Landscape
461+ to automatically apply security updates to most of the system,
462+ whilst holding back any potentially service affecting updates.
463+
464+* `install_sources`
465+
466+ A list of apt sources containing the packages that need to be installed.
467+ Each source may be either a line that can be added directly to
468+ sources.list(5), or in the form ppa:<user>/<ppa-name> for adding
469+ Personal Package Archives, or a distribution component to enable.
470+ The list is a yaml list, encoded as a string. The nicest way of
471+ declaring this in a yaml file looks like the following (in particular,
472+ the | character indicates that the value is a multiline string):
473+
474+ ```yaml
475+ install_sources: |
476+ - ppa:stub/cassandra
477+ - deb http://www.apache.org/dist/cassandra/debian 21x main
478+ ```
479+
480+* `install_keys`
481+
482+ A list of GPG signing keys to accept. There needs to be one entry
483+ per entry in install_sources. null may be used if no keep is
484+ needed, which is the case for PPAs and for the standard Ubuntu
485+ archives. Keys should be full ASCII armoured GPG public keys.
486+ GPG key ids are also accepted, but in most environments this
487+ mechanism is not secure. The install_keys list, like
488+ install_sources, must also be a yaml formatted list encoded as
489+ a string:
490+
491+ ```yaml
492+ install_keys: |
493+ - null
494+ - |
495+ -----BEGIN PGP PUBLIC KEY BLOCK-----
496+ Version: GnuPG v1
497+
498+ mQINBFQJvgUBEAC0KcYCTj0hd15p4fiXBsbob0sKgsvN5Lm7N9jzJWlGshJ0peMi
499+ kH8YhDXw5Lh+mPEHksL7t1L8CIr1a+ntns/Opt65ZPO38ENVkOqEVAn9Z5sIoZsb
500+ AUeLlJzSeRLTKhcOugK7UcsQD2FHnMBJz50bxis9X7pjmnc/tWpjAGJfaWdjDIo=
501+ =yiQ4
502+ -----END PGP PUBLIC KEY BLOCK-----
503+ ```
504+
505+## Usage
506+
507+Queue packages for installation, and have handlers waiting for
508+these packages to finish being installed:
509+
510+```python
511+import charms.apt
512+
513+@hook('install')
514+def install():
515+ charms.apt.queue_install(['git'])
516+
517+@when_not('apt.installed.gnupg')
518+def install_gnupg():
519+ charms.apt.queue_install(['gnupg'])
520+
521+@when('apt.installed.git')
522+@when('apt.installed.gnupg')
523+def grabit():
524+ clone_repo()
525+ validate_repo()
526+```
527+
528+### API
529+
530+Several methods are exposed in the charms.apt Python package.
531+
532+* `add_source(source, key=None)`
533+
534+ Add an apt source.
535+
536+ A source may be either a line that can be added directly to
537+ sources.list(5), or in the form ppa:<user>/<ppa-name> for adding
538+ Personal Package Archives, or a distribution component to enable.
539+
540+ The package signing key should be an ASCII armoured GPG key. While
541+ GPG key ids are also supported, the retrieval mechanism is insecure.
542+ There is no need to specify the package signing key for PPAs or for
543+ the main Ubuntu archives.
544+
545+ It is preferable if charms do not call this directly to hard
546+ coded apt sources, but instead have these sources listed
547+ as defaults in the install_sources config option. This allows
548+ operators to mirror your packages to internal archives and
549+ deploy your charm in environments without network access.
550+
551+ Sets the `apt.needs_update` reactive state.
552+
553+* `queue_install(packages, options=None)`
554+
555+ Queue one or more deb packages for install. The actual package
556+ installation will be performed later by a handler in the
557+ apt layer. The `apt.installed.{name}` state will be set once
558+ the package installed (one state for each package).
559+
560+ If a package has already been installed it will not be reinstalled.
561+
562+ If a package has already been queued it will not be requeued, and
563+ the install options will not be changed.
564+
565+* `installed()`
566+
567+ Returns the set of deb packages installed by this layer.
568+
569+* `purge(packages)`
570+
571+ Purge one or more deb packages from the system
572+
573+
574+### Extras
575+
576+These methods are called automatically by the reactive framework as
577+reactive state demands. However, you can also invoke them directly
578+if you want the operation done right now.
579+
580+* `update()`
581+
582+ Update the apt cache. Removes the `apt.needs_update` state.
583+
584+
585+* `install_queued()`
586+
587+ Installs deb packages queued for installation. On success, removes
588+ the `apt.queued_installs` state, sets the `apt.installed.{packagename}`
589+ state for each installed package, and returns True. On failure,
590+ sets the unit workload status to blocked and returns False.
591+ The package installs remain queued.
592+
593+
594+### Layer Options
595+
596+Packages can be specified at charm-build time in `layer.yaml`. List the
597+packages in the 'basic' or 'apt' sections.
598+
599+```yaml
600+includes:
601+ - layer:basic
602+ - layer:apt
603+options:
604+ basic:
605+ packages:
606+ - python3-psycopg2
607+ apt:
608+ packages:
609+ - git
610+ - bzr
611+```
612+
613+Packages required to import your Python reactive handlers should go
614+under 'basic'. These get installed by the base layer very early during
615+charm bootstrap, and only packages available in the main Ubuntu archive
616+can go here. Other packages should go under 'apt'. These will be
617+installed later, after custom apt sources such as PPAs have been added
618+from the `install_sources` configuration option.
619+
620+
621+## Support
622+
623+This layer is maintained on Launchpad by
624+Stuart Bishop (stuart.bishop@canonical.com).
625+
626+Code is available using git at git+ssh://git.launchpad.net/layer-apt.
627+
628+Bug reports can be made at https://bugs.launchpad.net/layer-apt.
629+
630+Queries and comments can be made on the Juju mailing list, Juju IRC
631+channels, or at https://answers.launchpad.net/layer-apt.
632
633=== added file 'deps/layer/layer-apt/config.yaml'
634--- deps/layer/layer-apt/config.yaml 1970-01-01 00:00:00 +0000
635+++ deps/layer/layer-apt/config.yaml 2017-02-15 08:51:47 +0000
636@@ -0,0 +1,34 @@
637+options:
638+ extra_packages:
639+ description: >
640+ Space separated list of extra deb packages to install.
641+ type: string
642+ default: ""
643+ package_status:
644+ default: "install"
645+ type: string
646+ description: >
647+ The status of service-affecting packages will be set to this
648+ value in the dpkg database. Valid values are "install" and "hold".
649+ install_sources:
650+ description: >
651+ List of extra apt sources, per charm-helpers standard
652+ format (a yaml list of strings encoded as a string). Each source
653+ may be either a line that can be added directly to
654+ sources.list(5), or in the form ppa:<user>/<ppa-name> for adding
655+ Personal Package Archives, or a distribution component to enable.
656+ type: string
657+ default: ""
658+ install_keys:
659+ description: >
660+ List of signing keys for install_sources package sources, per
661+ charmhelpers standard format (a yaml list of strings encoded as
662+ a string). The keys should be the full ASCII armoured GPG public
663+ keys. While GPG key ids are also supported and looked up on a
664+ keyserver, operators should be aware that this mechanism is
665+ insecure. null can be used if a standard package signing key is
666+ used that will already be installed on the machine, and for PPA
667+ sources where the package signing key is securely retrieved from
668+ Launchpad.
669+ type: string
670+ default: ""
671
672=== added file 'deps/layer/layer-apt/copyright'
673--- deps/layer/layer-apt/copyright 1970-01-01 00:00:00 +0000
674+++ deps/layer/layer-apt/copyright 2017-02-15 08:51:47 +0000
675@@ -0,0 +1,15 @@
676+Copyright 2015-2016 Canonical Ltd.
677+
678+This file is part of the Apt layer for Juju.
679+
680+This program is free software: you can redistribute it and/or modify
681+it under the terms of the GNU General Public License version 3, as
682+published by the Free Software Foundation.
683+
684+This program is distributed in the hope that it will be useful, but
685+WITHOUT ANY WARRANTY; without even the implied warranties of
686+MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
687+PURPOSE. See the GNU General Public License for more details.
688+
689+You should have received a copy of the GNU General Public License
690+along with this program. If not, see <http://www.gnu.org/licenses/>.
691
692=== added file 'deps/layer/layer-apt/layer.yaml'
693--- deps/layer/layer-apt/layer.yaml 1970-01-01 00:00:00 +0000
694+++ deps/layer/layer-apt/layer.yaml 2017-02-15 08:51:47 +0000
695@@ -0,0 +1,5 @@
696+defines:
697+ packages:
698+ type: array
699+ default: []
700+ description: Additional packages to be installed at time of bootstrap
701
702=== added directory 'deps/layer/layer-apt/lib'
703=== added directory 'deps/layer/layer-apt/lib/charms'
704=== added file 'deps/layer/layer-apt/lib/charms/__init__.py'
705--- deps/layer/layer-apt/lib/charms/__init__.py 1970-01-01 00:00:00 +0000
706+++ deps/layer/layer-apt/lib/charms/__init__.py 2017-02-15 08:51:47 +0000
707@@ -0,0 +1,2 @@
708+from pkgutil import extend_path
709+__path__ = extend_path(__path__, __name__)
710
711=== added file 'deps/layer/layer-apt/lib/charms/apt.py'
712--- deps/layer/layer-apt/lib/charms/apt.py 1970-01-01 00:00:00 +0000
713+++ deps/layer/layer-apt/lib/charms/apt.py 2017-02-15 08:51:47 +0000
714@@ -0,0 +1,182 @@
715+# Copyright 2015-2016 Canonical Ltd.
716+#
717+# This file is part of the Apt layer for Juju.
718+#
719+# This program is free software: you can redistribute it and/or modify
720+# it under the terms of the GNU General Public License version 3, as
721+# published by the Free Software Foundation.
722+#
723+# This program is distributed in the hope that it will be useful, but
724+# WITHOUT ANY WARRANTY; without even the implied warranties of
725+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
726+# PURPOSE. See the GNU General Public License for more details.
727+#
728+# You should have received a copy of the GNU General Public License
729+# along with this program. If not, see <http://www.gnu.org/licenses/>.
730+
731+'''
732+charms.reactive helpers for dealing with deb packages.
733+
734+Add apt package sources using add_source(). Queue deb packages for
735+installation with install(). Configure and work with your software
736+once the apt.installed.{packagename} state is set.
737+'''
738+import itertools
739+import subprocess
740+
741+from charmhelpers import fetch
742+from charmhelpers.core import hookenv, unitdata
743+from charms import reactive
744+
745+
746+__all__ = ['add_source', 'update', 'queue_install', 'install_queued',
747+ 'installed', 'purge', 'ensure_package_status']
748+
749+
750+def add_source(source, key=None):
751+ '''Add an apt source.
752+
753+ Sets the apt.needs_update state.
754+
755+ A source may be either a line that can be added directly to
756+ sources.list(5), or in the form ppa:<user>/<ppa-name> for adding
757+ Personal Package Archives, or a distribution component to enable.
758+
759+ The package signing key should be an ASCII armoured GPG key. While
760+ GPG key ids are also supported, the retrieval mechanism is insecure.
761+ There is no need to specify the package signing key for PPAs or for
762+ the main Ubuntu archives.
763+ '''
764+ # Maybe we should remember which sources have been added already
765+ # so we don't waste time re-adding them. Is this time significant?
766+ fetch.add_source(source, key)
767+ reactive.set_state('apt.needs_update')
768+
769+
770+def queue_install(packages, options=None):
771+ """Queue one or more deb packages for install.
772+
773+ The `apt.installed.{name}` state is set once the package is installed.
774+
775+ If a package has already been installed it will not be reinstalled.
776+
777+ If a package has already been queued it will not be requeued, and
778+ the install options will not be changed.
779+
780+ Sets the apt.queued_installs state.
781+ """
782+ if isinstance(packages, str):
783+ packages = [packages]
784+ # Filter installed packages.
785+ store = unitdata.kv()
786+ queued_packages = store.getrange('apt.install_queue.', strip=True)
787+ packages = {package: options for package in packages
788+ if not (package in queued_packages or
789+ reactive.helpers.is_state('apt.installed.' + package))}
790+ if packages:
791+ unitdata.kv().update(packages, prefix='apt.install_queue.')
792+ reactive.set_state('apt.queued_installs')
793+
794+
795+def installed():
796+ '''Return the set of deb packages completed install'''
797+ return set(state.split('.', 2)[2] for state in reactive.bus.get_states()
798+ if state.startswith('apt.installed.'))
799+
800+
801+def purge(packages):
802+ """Purge one or more deb packages from the system"""
803+ fetch.apt_purge(packages, fatal=True)
804+ store = unitdata.kv()
805+ store.unsetrange(packages, prefix='apt.install_queue.')
806+ for package in packages:
807+ reactive.remove_state('apt.installed.{}'.format(package))
808+
809+
810+def update():
811+ """Update the apt cache.
812+
813+ Removes the apt.needs_update state.
814+ """
815+ status_set(None, 'Updating apt cache')
816+ fetch.apt_update(fatal=True) # Friends don't let friends set fatal=False
817+ reactive.remove_state('apt.needs_update')
818+
819+
820+def install_queued():
821+ '''Installs queued deb packages.
822+
823+ Removes the apt.queued_installs state and sets the apt.installed state.
824+
825+ On failure, sets the unit's workload state to 'blocked' and returns
826+ False. Package installs remain queued.
827+
828+ On success, sets the apt.installed.{packagename} state for each
829+ installed package and returns True.
830+ '''
831+ store = unitdata.kv()
832+ queue = sorted((options, package)
833+ for package, options in store.getrange('apt.install_queue.',
834+ strip=True).items())
835+
836+ installed = set()
837+ for options, batch in itertools.groupby(queue, lambda x: x[0]):
838+ packages = [b[1] for b in batch]
839+ try:
840+ status_set(None, 'Installing {}'.format(','.join(packages)))
841+ fetch.apt_install(packages, options, fatal=True)
842+ store.unsetrange(packages, prefix='apt.install_queue.')
843+ installed.update(packages)
844+ except subprocess.CalledProcessError:
845+ status_set('blocked',
846+ 'Unable to install packages {}'
847+ .format(','.join(packages)))
848+ return False # Without setting reactive state.
849+
850+ for package in installed:
851+ reactive.set_state('apt.installed.{}'.format(package))
852+
853+ reactive.remove_state('apt.queued_installs')
854+ return True
855+
856+
857+def ensure_package_status():
858+ '''Hold or unhold packages per the package_status configuration option.
859+
860+ All packages installed using this module and handlers are affected.
861+
862+ An mechanism may be added in the future to override this for a
863+ subset of installed packages.
864+ '''
865+ packages = installed()
866+ if not packages:
867+ return
868+ config = hookenv.config()
869+ package_status = config['package_status']
870+ changed = reactive.helpers.data_changed('apt.package_status',
871+ (package_status, sorted(packages)))
872+ if changed:
873+ if package_status == 'hold':
874+ hookenv.log('Holding packages {}'.format(','.join(packages)))
875+ fetch.apt_hold(packages)
876+ else:
877+ hookenv.log('Unholding packages {}'.format(','.join(packages)))
878+ fetch.apt_unhold(packages)
879+ reactive.remove_state('apt.needs_hold')
880+
881+
882+def status_set(state, message):
883+ """Set the unit's workload status.
884+
885+ Set state == None to keep the same state and just change the message.
886+ """
887+ if state is None:
888+ state = hookenv.status_get()[0]
889+ if state == 'unknown':
890+ state = 'maintenance' # Guess
891+ if state in ('error', 'blocked'):
892+ lvl = hookenv.WARNING
893+ else:
894+ lvl = hookenv.INFO
895+ hookenv.status_set(state, message)
896+ hookenv.log('{}: {}'.format(state, message), lvl)
897
898=== added directory 'deps/layer/layer-apt/reactive'
899=== added file 'deps/layer/layer-apt/reactive/apt.py'
900--- deps/layer/layer-apt/reactive/apt.py 1970-01-01 00:00:00 +0000
901+++ deps/layer/layer-apt/reactive/apt.py 2017-02-15 08:51:47 +0000
902@@ -0,0 +1,131 @@
903+# Copyright 2015-2016 Canonical Ltd.
904+#
905+# This file is part of the Apt layer for Juju.
906+#
907+# This program is free software: you can redistribute it and/or modify
908+# it under the terms of the GNU General Public License version 3, as
909+# published by the Free Software Foundation.
910+#
911+# This program is distributed in the hope that it will be useful, but
912+# WITHOUT ANY WARRANTY; without even the implied warranties of
913+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
914+# PURPOSE. See the GNU General Public License for more details.
915+#
916+# You should have received a copy of the GNU General Public License
917+# along with this program. If not, see <http://www.gnu.org/licenses/>.
918+
919+'''
920+charms.reactive helpers for dealing with deb packages.
921+
922+Add apt package sources using add_source(). Queue deb packages for
923+installation with install(). Configure and work with your software
924+once the apt.installed.{packagename} state is set.
925+'''
926+import subprocess
927+
928+from charmhelpers import fetch
929+from charmhelpers.core import hookenv
930+from charmhelpers.core.hookenv import WARNING
931+from charms import layer
932+from charms import reactive
933+from charms.reactive import when, when_not
934+
935+import charms.apt
936+
937+
938+@when('apt.needs_update')
939+def update():
940+ charms.apt.update()
941+
942+
943+@when('apt.queued_installs')
944+@when_not('apt.needs_update')
945+def install_queued():
946+ charms.apt.install_queued()
947+
948+
949+@when_not('apt.queued_installs')
950+def ensure_package_status():
951+ charms.apt.ensure_package_status()
952+
953+
954+def filter_installed_packages(packages):
955+ # Don't use fetch.filter_installed_packages, as it depends on python-apt
956+ # and not available if the basic layer's use_site_packages option is off
957+ # TODO: Move this to charm-helpers.fetch
958+ cmd = ['dpkg-query', '--show', r'--showformat=${Package}\n']
959+ installed = set(subprocess.check_output(cmd,
960+ universal_newlines=True).split())
961+ return set(packages) - installed
962+
963+
964+def clear_removed_package_states():
965+ """On hook startup, clear install states for removed packages."""
966+ removed = filter_installed_packages(charms.apt.installed())
967+ if removed:
968+ hookenv.log('{} missing packages ({})'.format(len(removed),
969+ ','.join(removed)),
970+ WARNING)
971+ for package in removed:
972+ reactive.remove_state('apt.installed.{}'.format(package))
973+
974+
975+def configure_sources():
976+ """Add user specified package sources from the service configuration.
977+
978+ See charmhelpers.fetch.configure_sources for details.
979+ """
980+ config = hookenv.config()
981+
982+ # We don't have enums, so we need to validate this ourselves.
983+ package_status = config.get('package_status')
984+ if package_status not in ('hold', 'install'):
985+ charms.apt.status_set('blocked',
986+ 'Unknown package_status {}'
987+ ''.format(package_status))
988+ # Die before further hooks are run. This isn't very nice, but
989+ # there is no other way to inform the operator that they have
990+ # invalid configuration.
991+ raise SystemExit(0)
992+
993+ sources = config.get('install_sources')
994+ keys = config.get('install_keys')
995+ if reactive.helpers.data_changed('apt.configure_sources', (sources, keys)):
996+ fetch.configure_sources(update=False,
997+ sources_var='install_sources',
998+ keys_var='install_keys')
999+ reactive.set_state('apt.needs_update')
1000+
1001+ extra_packages = sorted(config.get('extra_packages', '').split())
1002+ if extra_packages:
1003+ charms.apt.queue_install(extra_packages)
1004+
1005+
1006+def queue_layer_packages():
1007+ """Add packages listed in build-time layer options."""
1008+ # Both basic and apt layer. basic layer will have already installed
1009+ # its defined packages, but rescheduling it here gets the apt layer
1010+ # state set and they will pinned as any other apt layer installed
1011+ # package.
1012+ opts = layer.options()
1013+ for section in ['basic', 'apt']:
1014+ if section in opts and 'packages' in opts[section]:
1015+ charms.apt.queue_install(opts[section]['packages'])
1016+
1017+
1018+# Per https://github.com/juju-solutions/charms.reactive/issues/33,
1019+# this module may be imported multiple times so ensure the
1020+# initialization hook is only registered once. I have to piggy back
1021+# onto the namespace of a module imported before reactive discovery
1022+# to do this.
1023+if not hasattr(reactive, '_apt_registered'):
1024+ # We need to register this to run every hook, not just during install
1025+ # and config-changed, to protect against race conditions. If we don't
1026+ # do this, then the config in the hook environment may show updates
1027+ # to running hooks well before the config-changed hook has been invoked
1028+ # and the intialization provided an opertunity to be run.
1029+ hookenv.atstart(hookenv.log, 'Initializing Apt Layer')
1030+ hookenv.atstart(clear_removed_package_states)
1031+ hookenv.atstart(configure_sources)
1032+ hookenv.atstart(queue_layer_packages)
1033+ reactive._apt_registered = True
1034
1035=== added directory 'deps/layer/layer-basic'
1036=== added file 'deps/layer/layer-basic/.gitignore'
1037--- deps/layer/layer-basic/.gitignore 1970-01-01 00:00:00 +0000
1038+++ deps/layer/layer-basic/.gitignore 2017-02-15 08:51:47 +0000
1039@@ -0,0 +1,5 @@
1040+*.pyc
1041+*~
1042+.ropeproject
1043+.settings
1044+.tox
1045
1046=== added file 'deps/layer/layer-basic/Makefile'
1047--- deps/layer/layer-basic/Makefile 1970-01-01 00:00:00 +0000
1048+++ deps/layer/layer-basic/Makefile 2017-02-15 08:51:47 +0000
1049@@ -0,0 +1,24 @@
1050+#!/usr/bin/make
1051+
1052+all: lint unit_test
1053+
1054+
1055+.PHONY: clean
1056+clean:
1057+ @rm -rf .tox
1058+
1059+.PHONY: apt_prereqs
1060+apt_prereqs:
1061+ @# Need tox, but don't install the apt version unless we have to (don't want to conflict with pip)
1062+ @which tox >/dev/null || (sudo apt-get install -y python-pip && sudo pip install tox)
1063+
1064+.PHONY: lint
1065+lint: apt_prereqs
1066+ @tox --notest
1067+ @PATH=.tox/py34/bin:.tox/py35/bin flake8 $(wildcard hooks reactive lib unit_tests tests)
1068+ @charm proof
1069+
1070+.PHONY: unit_test
1071+unit_test: apt_prereqs
1072+ @echo Starting tests...
1073+ tox
1074
1075=== added file 'deps/layer/layer-basic/README.md'
1076--- deps/layer/layer-basic/README.md 1970-01-01 00:00:00 +0000
1077+++ deps/layer/layer-basic/README.md 2017-02-15 08:51:47 +0000
1078@@ -0,0 +1,221 @@
1079+# Overview
1080+
1081+This is the base layer for all charms [built using layers][building]. It
1082+provides all of the standard Juju hooks and runs the
1083+[charms.reactive.main][charms.reactive] loop for them. It also bootstraps the
1084+[charm-helpers][] and [charms.reactive][] libraries and all of their
1085+dependencies for use by the charm.
1086+
1087+# Usage
1088+
1089+To create a charm layer using this base layer, you need only include it in
1090+a `layer.yaml` file:
1091+
1092+```yaml
1093+includes: ['layer:basic']
1094+```
1095+
1096+This will fetch this layer from [interfaces.juju.solutions][] and incorporate
1097+it into your charm layer. You can then add handlers under the `reactive/`
1098+directory. Note that **any** file under `reactive/` will be expected to
1099+contain handlers, whether as Python decorated functions or [executables][non-python]
1100+using the [external handler protocol][].
1101+
1102+### Charm Dependencies
1103+
1104+Each layer can include a `wheelhouse.txt` file with Python requirement lines.
1105+For example, this layer's `wheelhouse.txt` includes:
1106+
1107+```
1108+pip>=7.0.0,<8.0.0
1109+charmhelpers>=0.4.0,<1.0.0
1110+charms.reactive>=0.1.0,<2.0.0
1111+```
1112+
1113+All of these dependencies from each layer will be fetched (and updated) at build
1114+time and will be automatically installed by this base layer before any reactive
1115+handlers are run.
1116+
1117+Note that the `wheelhouse.txt` file is intended for **charm** dependencies only.
1118+That is, for libraries that the charm code itself needs to do its job of deploying
1119+and configuring the payload. If the payload itself has Python dependencies, those
1120+should be handled separately, by the charm.
1121+
1122+See [PyPI][pypi charms.X] for packages under the `charms.` namespace which might
1123+be useful for your charm.
1124+
1125+### Layer Namespace
1126+
1127+Each layer has a reserved section in the `charms.layer.` Python package namespace,
1128+which it can populate by including a `lib/charms/layer/<layer-name>.py` file or
1129+by placing files under `lib/charms/layer/<layer-name>/`. (If the layer name
1130+includes hyphens, replace them with underscores.) These can be helpers that the
1131+layer uses internally, or it can expose classes or functions to be used by other
1132+layers to interact with that layer.
1133+
1134+For example, a layer named `foo` could include a `lib/charms/layer/foo.py` file
1135+with some helper functions that other layers could access using:
1136+
1137+```python
1138+from charms.layer.foo import my_helper
1139+```
1140+
1141+### Layer Options
1142+
1143+Any layer can define options in its `layer.yaml`. Those options can then be set
1144+by other layers to change the behavior of your layer. The options are defined
1145+using [jsonschema][], which is the same way that [action paramters][] are defined.
1146+
1147+For example, the `foo` layer could include the following option definitons:
1148+
1149+```yaml
1150+includes: ['layer:basic']
1151+defines: # define some options for this layer (the layer "foo")
1152+ enable-bar: # define an "enable-bar" option for this layer
1153+ description: If true, enable support for "bar".
1154+ type: boolean
1155+ default: false
1156+```
1157+
1158+A layer using `foo` could then set it:
1159+
1160+```yaml
1161+includes: ['layer:foo']
1162+options:
1163+ foo: # setting options for the "foo" layer
1164+ enable-bar: true # set the "enable-bar" option to true
1165+```
1166+
1167+The `foo` layer can then use the `charms.layer.options` helper to load the values
1168+for the options that it defined. For example:
1169+
1170+```python
1171+from charms import layer
1172+
1173+@when('state')
1174+def do_thing():
1175+ layer_opts = layer.options('foo') # load all of the options for the "foo" layer
1176+ if layer_opts['enable-bar']: # check the value of the "enable-bar" option
1177+ hookenv.log("Bar is enabled")
1178+```
1179+
1180+You can also access layer options in other handlers, such as Bash, using
1181+the command-line interface:
1182+
1183+```bash
1184+. charms.reactive.sh
1185+
1186+@when 'state'
1187+function do_thing() {
1188+ if layer_option foo enable-bar; then
1189+ juju-log "Bar is enabled"
1190+ juju-log "bar-value is: $(layer_option foo bar-value)"
1191+ fi
1192+}
1193+
1194+reactive_handler_main
1195+```
1196+
1197+Note that options of type `boolean` will set the exit code, while other types
1198+will be printed out.
1199+
1200+# Hooks
1201+
1202+This layer provides hooks that other layers can react to using the decorators
1203+of the [charms.reactive][] library:
1204+
1205+ * `config-changed`
1206+ * `install`
1207+ * `leader-elected`
1208+ * `leader-settings-changed`
1209+ * `start`
1210+ * `stop`
1211+ * `upgrade-charm`
1212+ * `update-status`
1213+
1214+Other hooks are not implemented at this time. A new layer can implement storage
1215+or relation hooks in their own layer by putting them in the `hooks` directory.
1216+
1217+**Note:** Because `update-status` is invoked every 5 minutes, you should take
1218+care to ensure that your reactive handlers only invoke expensive operations
1219+when absolutely necessary. It is recommended that you use helpers like
1220+[`@only_once`][], [`@when_file_changed`][], and [`data_changed`][] to ensure
1221+that handlers run only when necessary.
1222+
1223+# Layer Configuration
1224+
1225+This layer supports the following options, which can be set in `layer.yaml`:
1226+
1227+ * **packages** A list of system packages to be installed before the reactive
1228+ handlers are invoked.
1229+
1230+ * **use_venv** If set to true, the charm dependencies from the various
1231+ layers' `wheelhouse.txt` files will be installed in a Python virtualenv
1232+ located at `$CHARM_DIR/../.venv`. This keeps charm dependencies from
1233+ conflicting with payload dependencies, but you must take care to preserve
1234+ the environment and interpreter if using `execl` or `subprocess`.
1235+
1236+ * **include_system_packages** If set to true and using a venv, include
1237+ the `--system-site-packages` options to make system Python libraries
1238+ visible within the venv.
1239+
1240+An example `layer.yaml` using these options might be:
1241+
1242+```yaml
1243+includes: ['layer:basic']
1244+options:
1245+ basic:
1246+ packages: ['git']
1247+ use_venv: true
1248+ include_system_packages: true
1249+```
1250+
1251+
1252+# Reactive States
1253+
1254+This layer will set the following states:
1255+
1256+ * **`config.changed`** Any config option has changed from its previous value.
1257+ This state is cleared automatically at the end of each hook invocation.
1258+
1259+ * **`config.changed.<option>`** A specific config option has changed.
1260+ **`<option>`** will be replaced by the config option name from `config.yaml`.
1261+ This state is cleared automatically at the end of each hook invocation.
1262+
1263+ * **`config.set.<option>`** A specific config option has a True or non-empty
1264+ value set. **`<option>`** will be replaced by the config option name from
1265+ `config.yaml`. This state is cleared automatically at the end of each hook
1266+ invocation.
1267+
1268+ * **`config.default.<option>`** A specific config option is set to its
1269+ default value. **`<option>`** will be replaced by the config option name
1270+ from `config.yaml`. This state is cleared automatically at the end of
1271+ each hook invocation.
1272+
1273+An example using the config states would be:
1274+
1275+```python
1276+@when('config.changed.my-opt')
1277+def my_opt_changed():
1278+ update_config()
1279+ restart_service()
1280+```
1281+
1282+
1283+# Actions
1284+
1285+This layer currently does not define any actions.
1286+
1287+
1288+[building]: https://jujucharms.com/docs/devel/authors-charm-building
1289+[charm-helpers]: https://pythonhosted.org/charmhelpers/
1290+[charms.reactive]: https://pythonhosted.org/charms.reactive/
1291+[interfaces.juju.solutions]: http://interfaces.juju.solutions/
1292+[non-python]: https://pythonhosted.org/charms.reactive/#non-python-reactive-handlers
1293+[external handler protocol]: https://pythonhosted.org/charms.reactive/charms.reactive.bus.html#charms.reactive.bus.ExternalHandler
1294+[jsonschema]: http://json-schema.org/
1295+[action paramters]: https://jujucharms.com/docs/stable/authors-charm-actions
1296+[pypi charms.X]: https://pypi.python.org/pypi?%3Aaction=search&term=charms.&submit=search
1297+[`@only_once`]: https://pythonhosted.org/charms.reactive/charms.reactive.decorators.html#charms.reactive.decorators.only_once
1298+[`@when_file_changed`]: https://pythonhosted.org/charms.reactive/charms.reactive.decorators.html#charms.reactive.decorators.when_file_changed
1299+[`data_changed`]: https://pythonhosted.org/charms.reactive/charms.reactive.helpers.html#charms.reactive.helpers.data_changed
1300
1301=== added directory 'deps/layer/layer-basic/bin'
1302=== added file 'deps/layer/layer-basic/bin/layer_option'
1303--- deps/layer/layer-basic/bin/layer_option 1970-01-01 00:00:00 +0000
1304+++ deps/layer/layer-basic/bin/layer_option 2017-02-15 08:51:47 +0000
1305@@ -0,0 +1,24 @@
1306+#!/usr/bin/env python3
1307+
1308+import sys
1309+sys.path.append('lib')
1310+
1311+import argparse
1312+from charms.layer import options
1313+
1314+
1315+parser = argparse.ArgumentParser(description='Access layer options.')
1316+parser.add_argument('section',
1317+ help='the section, or layer, the option is from')
1318+parser.add_argument('option',
1319+ help='the option to access')
1320+
1321+args = parser.parse_args()
1322+value = options(args.section).get(args.option, '')
1323+if isinstance(value, bool):
1324+ sys.exit(0 if value else 1)
1325+elif isinstance(value, list):
1326+ for val in value:
1327+ print(val)
1328+else:
1329+ print(value)
1330
1331=== added file 'deps/layer/layer-basic/copyright'
1332--- deps/layer/layer-basic/copyright 1970-01-01 00:00:00 +0000
1333+++ deps/layer/layer-basic/copyright 2017-02-15 08:51:47 +0000
1334@@ -0,0 +1,9 @@
1335+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0
1336+
1337+Files: *
1338+Copyright: 2015, Canonical Ltd.
1339+License: GPL-3
1340+
1341+License: GPL-3
1342+ On Debian GNU/Linux system you can find the complete text of the
1343+ GPL-3 license in '/usr/share/common-licenses/GPL-3'
1344
1345=== added directory 'deps/layer/layer-basic/hooks'
1346=== added file 'deps/layer/layer-basic/hooks/config-changed'
1347--- deps/layer/layer-basic/hooks/config-changed 1970-01-01 00:00:00 +0000
1348+++ deps/layer/layer-basic/hooks/config-changed 2017-02-15 08:51:47 +0000
1349@@ -0,0 +1,19 @@
1350+#!/usr/bin/env python3
1351+
1352+# Load modules from $CHARM_DIR/lib
1353+import sys
1354+sys.path.append('lib')
1355+
1356+from charms.layer import basic
1357+basic.bootstrap_charm_deps()
1358+basic.init_config_states()
1359+
1360+
1361+# This will load and run the appropriate @hook and other decorated
1362+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
1363+# and $CHARM_DIR/hooks/relations.
1364+#
1365+# See https://jujucharms.com/docs/stable/authors-charm-building
1366+# for more information on this pattern.
1367+from charms.reactive import main
1368+main()
1369
1370=== added file 'deps/layer/layer-basic/hooks/hook.template'
1371--- deps/layer/layer-basic/hooks/hook.template 1970-01-01 00:00:00 +0000
1372+++ deps/layer/layer-basic/hooks/hook.template 2017-02-15 08:51:47 +0000
1373@@ -0,0 +1,19 @@
1374+#!/usr/bin/env python3
1375+
1376+# Load modules from $CHARM_DIR/lib
1377+import sys
1378+sys.path.append('lib')
1379+
1380+from charms.layer import basic
1381+basic.bootstrap_charm_deps()
1382+basic.init_config_states()
1383+
1384+
1385+# This will load and run the appropriate @hook and other decorated
1386+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
1387+# and $CHARM_DIR/hooks/relations.
1388+#
1389+# See https://jujucharms.com/docs/stable/authors-charm-building
1390+# for more information on this pattern.
1391+from charms.reactive import main
1392+main()
1393
1394=== added file 'deps/layer/layer-basic/hooks/install'
1395--- deps/layer/layer-basic/hooks/install 1970-01-01 00:00:00 +0000
1396+++ deps/layer/layer-basic/hooks/install 2017-02-15 08:51:47 +0000
1397@@ -0,0 +1,19 @@
1398+#!/usr/bin/env python3
1399+
1400+# Load modules from $CHARM_DIR/lib
1401+import sys
1402+sys.path.append('lib')
1403+
1404+from charms.layer import basic
1405+basic.bootstrap_charm_deps()
1406+basic.init_config_states()
1407+
1408+
1409+# This will load and run the appropriate @hook and other decorated
1410+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
1411+# and $CHARM_DIR/hooks/relations.
1412+#
1413+# See https://jujucharms.com/docs/stable/authors-charm-building
1414+# for more information on this pattern.
1415+from charms.reactive import main
1416+main()
1417
1418=== added file 'deps/layer/layer-basic/hooks/leader-elected'
1419--- deps/layer/layer-basic/hooks/leader-elected 1970-01-01 00:00:00 +0000
1420+++ deps/layer/layer-basic/hooks/leader-elected 2017-02-15 08:51:47 +0000
1421@@ -0,0 +1,19 @@
1422+#!/usr/bin/env python3
1423+
1424+# Load modules from $CHARM_DIR/lib
1425+import sys
1426+sys.path.append('lib')
1427+
1428+from charms.layer import basic
1429+basic.bootstrap_charm_deps()
1430+basic.init_config_states()
1431+
1432+
1433+# This will load and run the appropriate @hook and other decorated
1434+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
1435+# and $CHARM_DIR/hooks/relations.
1436+#
1437+# See https://jujucharms.com/docs/stable/authors-charm-building
1438+# for more information on this pattern.
1439+from charms.reactive import main
1440+main()
1441
1442=== added file 'deps/layer/layer-basic/hooks/leader-settings-changed'
1443--- deps/layer/layer-basic/hooks/leader-settings-changed 1970-01-01 00:00:00 +0000
1444+++ deps/layer/layer-basic/hooks/leader-settings-changed 2017-02-15 08:51:47 +0000
1445@@ -0,0 +1,19 @@
1446+#!/usr/bin/env python3
1447+
1448+# Load modules from $CHARM_DIR/lib
1449+import sys
1450+sys.path.append('lib')
1451+
1452+from charms.layer import basic
1453+basic.bootstrap_charm_deps()
1454+basic.init_config_states()
1455+
1456+
1457+# This will load and run the appropriate @hook and other decorated
1458+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
1459+# and $CHARM_DIR/hooks/relations.
1460+#
1461+# See https://jujucharms.com/docs/stable/authors-charm-building
1462+# for more information on this pattern.
1463+from charms.reactive import main
1464+main()
1465
1466=== added file 'deps/layer/layer-basic/hooks/start'
1467--- deps/layer/layer-basic/hooks/start 1970-01-01 00:00:00 +0000
1468+++ deps/layer/layer-basic/hooks/start 2017-02-15 08:51:47 +0000
1469@@ -0,0 +1,19 @@
1470+#!/usr/bin/env python3
1471+
1472+# Load modules from $CHARM_DIR/lib
1473+import sys
1474+sys.path.append('lib')
1475+
1476+from charms.layer import basic
1477+basic.bootstrap_charm_deps()
1478+basic.init_config_states()
1479+
1480+
1481+# This will load and run the appropriate @hook and other decorated
1482+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
1483+# and $CHARM_DIR/hooks/relations.
1484+#
1485+# See https://jujucharms.com/docs/stable/authors-charm-building
1486+# for more information on this pattern.
1487+from charms.reactive import main
1488+main()
1489
1490=== added file 'deps/layer/layer-basic/hooks/stop'
1491--- deps/layer/layer-basic/hooks/stop 1970-01-01 00:00:00 +0000
1492+++ deps/layer/layer-basic/hooks/stop 2017-02-15 08:51:47 +0000
1493@@ -0,0 +1,19 @@
1494+#!/usr/bin/env python3
1495+
1496+# Load modules from $CHARM_DIR/lib
1497+import sys
1498+sys.path.append('lib')
1499+
1500+from charms.layer import basic
1501+basic.bootstrap_charm_deps()
1502+basic.init_config_states()
1503+
1504+
1505+# This will load and run the appropriate @hook and other decorated
1506+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
1507+# and $CHARM_DIR/hooks/relations.
1508+#
1509+# See https://jujucharms.com/docs/stable/authors-charm-building
1510+# for more information on this pattern.
1511+from charms.reactive import main
1512+main()
1513
1514=== added file 'deps/layer/layer-basic/hooks/update-status'
1515--- deps/layer/layer-basic/hooks/update-status 1970-01-01 00:00:00 +0000
1516+++ deps/layer/layer-basic/hooks/update-status 2017-02-15 08:51:47 +0000
1517@@ -0,0 +1,19 @@
1518+#!/usr/bin/env python3
1519+
1520+# Load modules from $CHARM_DIR/lib
1521+import sys
1522+sys.path.append('lib')
1523+
1524+from charms.layer import basic
1525+basic.bootstrap_charm_deps()
1526+basic.init_config_states()
1527+
1528+
1529+# This will load and run the appropriate @hook and other decorated
1530+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
1531+# and $CHARM_DIR/hooks/relations.
1532+#
1533+# See https://jujucharms.com/docs/stable/authors-charm-building
1534+# for more information on this pattern.
1535+from charms.reactive import main
1536+main()
1537
1538=== added file 'deps/layer/layer-basic/hooks/upgrade-charm'
1539--- deps/layer/layer-basic/hooks/upgrade-charm 1970-01-01 00:00:00 +0000
1540+++ deps/layer/layer-basic/hooks/upgrade-charm 2017-02-15 08:51:47 +0000
1541@@ -0,0 +1,28 @@
1542+#!/usr/bin/env python3
1543+
1544+# Load modules from $CHARM_DIR/lib
1545+import os
1546+import sys
1547+sys.path.append('lib')
1548+
1549+# This is an upgrade-charm context, make sure we install latest deps
1550+if not os.path.exists('wheelhouse/.upgrade'):
1551+ open('wheelhouse/.upgrade', 'w').close()
1552+ if os.path.exists('wheelhouse/.bootstrapped'):
1553+ os.unlink('wheelhouse/.bootstrapped')
1554+else:
1555+ os.unlink('wheelhouse/.upgrade')
1556+
1557+from charms.layer import basic
1558+basic.bootstrap_charm_deps()
1559+basic.init_config_states()
1560+
1561+
1562+# This will load and run the appropriate @hook and other decorated
1563+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
1564+# and $CHARM_DIR/hooks/relations.
1565+#
1566+# See https://jujucharms.com/docs/stable/authors-charm-building
1567+# for more information on this pattern.
1568+from charms.reactive import main
1569+main()
1570
1571=== added file 'deps/layer/layer-basic/layer.yaml'
1572--- deps/layer/layer-basic/layer.yaml 1970-01-01 00:00:00 +0000
1573+++ deps/layer/layer-basic/layer.yaml 2017-02-15 08:51:47 +0000
1574@@ -0,0 +1,18 @@
1575+defines:
1576+ packages:
1577+ type: array
1578+ default: []
1579+ description: Additional packages to be installed at time of bootstrap
1580+ use_venv:
1581+ type: boolean
1582+ default: false
1583+ description: >
1584+ Install charm dependencies (wheelhouse) into a Python virtual environment
1585+ to help avoid conflicts with other charms or libraries on the machine.
1586+ include_system_packages:
1587+ type: boolean
1588+ default: false
1589+ description: >
1590+ If using a virtual environment, allow the venv to see Python packages
1591+ installed at the system level. This reduces isolation, but is necessary
1592+ to use Python packages installed via apt-get.
1593
1594=== added directory 'deps/layer/layer-basic/lib'
1595=== added directory 'deps/layer/layer-basic/lib/charms'
1596=== added directory 'deps/layer/layer-basic/lib/charms/layer'
1597=== added file 'deps/layer/layer-basic/lib/charms/layer/__init__.py'
1598--- deps/layer/layer-basic/lib/charms/layer/__init__.py 1970-01-01 00:00:00 +0000
1599+++ deps/layer/layer-basic/lib/charms/layer/__init__.py 2017-02-15 08:51:47 +0000
1600@@ -0,0 +1,21 @@
1601+import os
1602+
1603+
1604+class LayerOptions(dict):
1605+ def __init__(self, layer_file, section=None):
1606+ import yaml # defer, might not be available until bootstrap
1607+ with open(layer_file) as f:
1608+ layer = yaml.safe_load(f.read())
1609+ opts = layer.get('options', {})
1610+ if section and section in opts:
1611+ super(LayerOptions, self).__init__(opts.get(section))
1612+ else:
1613+ super(LayerOptions, self).__init__(opts)
1614+
1615+
1616+def options(section=None, layer_file=None):
1617+ if not layer_file:
1618+ base_dir = os.environ.get('CHARM_DIR', os.getcwd())
1619+ layer_file = os.path.join(base_dir, 'layer.yaml')
1620+
1621+ return LayerOptions(layer_file, section)
1622
1623=== added file 'deps/layer/layer-basic/lib/charms/layer/basic.py'
1624--- deps/layer/layer-basic/lib/charms/layer/basic.py 1970-01-01 00:00:00 +0000
1625+++ deps/layer/layer-basic/lib/charms/layer/basic.py 2017-02-15 08:51:47 +0000
1626@@ -0,0 +1,159 @@
1627+import os
1628+import sys
1629+import shutil
1630+import platform
1631+from glob import glob
1632+from subprocess import check_call
1633+
1634+from charms.layer.execd import execd_preinstall
1635+
1636+
1637+def bootstrap_charm_deps():
1638+ """
1639+ Set up the base charm dependencies so that the reactive system can run.
1640+ """
1641+ # execd must happen first, before any attempt to install packages or
1642+ # access the network, because sites use this hook to do bespoke
1643+ # configuration and install secrets so the rest of this bootstrap
1644+ # and the charm itself can actually succeed. This call does nothing
1645+ # unless the operator has created and populated $CHARM_DIR/exec.d.
1646+ execd_preinstall()
1647+ # ensure that $CHARM_DIR/bin is on the path, for helper scripts
1648+ os.environ['PATH'] += ':%s' % os.path.join(os.environ['CHARM_DIR'], 'bin')
1649+ venv = os.path.abspath('../.venv')
1650+ vbin = os.path.join(venv, 'bin')
1651+ vpip = os.path.join(vbin, 'pip')
1652+ vpy = os.path.join(vbin, 'python')
1653+ if os.path.exists('wheelhouse/.bootstrapped'):
1654+ from charms import layer
1655+ cfg = layer.options('basic')
1656+ if cfg.get('use_venv') and '.venv' not in sys.executable:
1657+ # activate the venv
1658+ os.environ['PATH'] = ':'.join([vbin, os.environ['PATH']])
1659+ reload_interpreter(vpy)
1660+ return
1661+ # bootstrap wheelhouse
1662+ if os.path.exists('wheelhouse'):
1663+ with open('/root/.pydistutils.cfg', 'w') as fp:
1664+ # make sure that easy_install also only uses the wheelhouse
1665+ # (see https://github.com/pypa/pip/issues/410)
1666+ charm_dir = os.environ['CHARM_DIR']
1667+ fp.writelines([
1668+ "[easy_install]\n",
1669+ "allow_hosts = ''\n",
1670+ "find_links = file://{}/wheelhouse/\n".format(charm_dir),
1671+ ])
1672+ apt_install(['python3-pip', 'python3-setuptools', 'python3-yaml'])
1673+ from charms import layer
1674+ cfg = layer.options('basic')
1675+ # include packages defined in layer.yaml
1676+ apt_install(cfg.get('packages', []))
1677+ # if we're using a venv, set it up
1678+ if cfg.get('use_venv'):
1679+ if not os.path.exists(venv):
1680+ distname, version, series = platform.linux_distribution()
1681+ if series in ('precise', 'trusty'):
1682+ apt_install(['python-virtualenv'])
1683+ else:
1684+ apt_install(['virtualenv'])
1685+ cmd = ['virtualenv', '-ppython3', '--never-download', venv]
1686+ if cfg.get('include_system_packages'):
1687+ cmd.append('--system-site-packages')
1688+ check_call(cmd)
1689+ os.environ['PATH'] = ':'.join([vbin, os.environ['PATH']])
1690+ pip = vpip
1691+ else:
1692+ pip = 'pip3'
1693+ # save a copy of system pip to prevent `pip3 install -U pip`
1694+ # from changing it
1695+ if os.path.exists('/usr/bin/pip'):
1696+ shutil.copy2('/usr/bin/pip', '/usr/bin/pip.save')
1697+ # need newer pip, to fix spurious Double Requirement error:
1698+ # https://github.com/pypa/pip/issues/56
1699+ check_call([pip, 'install', '-U', '--no-index', '-f', 'wheelhouse',
1700+ 'pip'])
1701+ # install the rest of the wheelhouse deps
1702+ check_call([pip, 'install', '-U', '--no-index', '-f', 'wheelhouse'] +
1703+ glob('wheelhouse/*'))
1704+ if not cfg.get('use_venv'):
1705+ # restore system pip to prevent `pip3 install -U pip`
1706+ # from changing it
1707+ if os.path.exists('/usr/bin/pip.save'):
1708+ shutil.copy2('/usr/bin/pip.save', '/usr/bin/pip')
1709+ os.remove('/usr/bin/pip.save')
1710+ os.remove('/root/.pydistutils.cfg')
1711+ # flag us as having already bootstrapped so we don't do it again
1712+ open('wheelhouse/.bootstrapped', 'w').close()
1713+ # Ensure that the newly bootstrapped libs are available.
1714+ # Note: this only seems to be an issue with namespace packages.
1715+ # Non-namespace-package libs (e.g., charmhelpers) are available
1716+ # without having to reload the interpreter. :/
1717+ reload_interpreter(vpy if cfg.get('use_venv') else sys.argv[0])
1718+
1719+
1720+def reload_interpreter(python):
1721+ """
1722+ Reload the python interpreter to ensure that all deps are available.
1723+
1724+ Newly installed modules in namespace packages sometimes seemt to
1725+ not be picked up by Python 3.
1726+ """
1727+ os.execle(python, python, sys.argv[0], os.environ)
1728+
1729+
1730+def apt_install(packages):
1731+ """
1732+ Install apt packages.
1733+
1734+ This ensures a consistent set of options that are often missed but
1735+ should really be set.
1736+ """
1737+ if isinstance(packages, (str, bytes)):
1738+ packages = [packages]
1739+
1740+ env = os.environ.copy()
1741+
1742+ if 'DEBIAN_FRONTEND' not in env:
1743+ env['DEBIAN_FRONTEND'] = 'noninteractive'
1744+
1745+ cmd = ['apt-get',
1746+ '--option=Dpkg::Options::=--force-confold',
1747+ '--assume-yes',
1748+ 'install']
1749+ check_call(cmd + packages, env=env)
1750+
1751+
1752+def init_config_states():
1753+ import yaml
1754+ from charmhelpers.core import hookenv
1755+ from charms.reactive import set_state
1756+ from charms.reactive import toggle_state
1757+ config = hookenv.config()
1758+ config_defaults = {}
1759+ config_defs = {}
1760+ config_yaml = os.path.join(hookenv.charm_dir(), 'config.yaml')
1761+ if os.path.exists(config_yaml):
1762+ with open(config_yaml) as fp:
1763+ config_defs = yaml.safe_load(fp).get('options', {})
1764+ config_defaults = {key: value.get('default')
1765+ for key, value in config_defs.items()}
1766+ for opt in config_defs.keys():
1767+ if config.changed(opt):
1768+ set_state('config.changed')
1769+ set_state('config.changed.{}'.format(opt))
1770+ toggle_state('config.set.{}'.format(opt), config.get(opt))
1771+ toggle_state('config.default.{}'.format(opt),
1772+ config.get(opt) == config_defaults[opt])
1773+ hookenv.atexit(clear_config_states)
1774+
1775+
1776+def clear_config_states():
1777+ from charmhelpers.core import hookenv, unitdata
1778+ from charms.reactive import remove_state
1779+ config = hookenv.config()
1780+ remove_state('config.changed')
1781+ for opt in config.keys():
1782+ remove_state('config.changed.{}'.format(opt))
1783+ remove_state('config.set.{}'.format(opt))
1784+ remove_state('config.default.{}'.format(opt))
1785+ unitdata.kv().flush()
1786
1787=== added file 'deps/layer/layer-basic/lib/charms/layer/execd.py'
1788--- deps/layer/layer-basic/lib/charms/layer/execd.py 1970-01-01 00:00:00 +0000
1789+++ deps/layer/layer-basic/lib/charms/layer/execd.py 2017-02-15 08:51:47 +0000
1790@@ -0,0 +1,138 @@
1791+# Copyright 2014-2016 Canonical Limited.
1792+#
1793+# This file is part of layer-basic, the reactive base layer for Juju.
1794+#
1795+# charm-helpers is free software: you can redistribute it and/or modify
1796+# it under the terms of the GNU Lesser General Public License version 3 as
1797+# published by the Free Software Foundation.
1798+#
1799+# charm-helpers is distributed in the hope that it will be useful,
1800+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1801+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1802+# GNU Lesser General Public License for more details.
1803+#
1804+# You should have received a copy of the GNU Lesser General Public License
1805+# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1806+
1807+# This module may only import from the Python standard library.
1808+import os
1809+import sys
1810+import subprocess
1811+import time
1812+
1813+'''
1814+execd/preinstall
1815+
1816+It is often necessary to configure and reconfigure machines
1817+after provisioning, but before attempting to run the charm.
1818+Common examples are specialized network configuration, enabling
1819+of custom hardware, non-standard disk partitioning and filesystems,
1820+adding secrets and keys required for using a secured network.
1821+
1822+The reactive framework's base layer invokes this mechanism as
1823+early as possible, before any network access is made or dependencies
1824+unpacked or non-standard modules imported (including the charms.reactive
1825+framework itself).
1826+
1827+Operators needing to use this functionality may branch a charm and
1828+create an exec.d directory in it. The exec.d directory in turn contains
1829+one or more subdirectories, each of which contains an executable called
1830+charm-pre-install and any other required resources. The charm-pre-install
1831+executables are run, and if successful, state saved so they will not be
1832+run again.
1833+
1834+ $CHARM_DIR/exec.d/mynamespace/charm-pre-install
1835+
1836+An alternative to branching a charm is to compose a new charm that contains
1837+the exec.d directory, using the original charm as a layer,
1838+
1839+A charm author could also abuse this mechanism to modify the charm
1840+environment in unusual ways, but for most purposes it is saner to use
1841+charmhelpers.core.hookenv.atstart().
1842+'''
1843+
1844+
1845+def default_execd_dir():
1846+ return os.path.join(os.environ['CHARM_DIR'], 'exec.d')
1847+
1848+
1849+def execd_module_paths(execd_dir=None):
1850+ """Generate a list of full paths to modules within execd_dir."""
1851+ if not execd_dir:
1852+ execd_dir = default_execd_dir()
1853+
1854+ if not os.path.exists(execd_dir):
1855+ return
1856+
1857+ for subpath in os.listdir(execd_dir):
1858+ module = os.path.join(execd_dir, subpath)
1859+ if os.path.isdir(module):
1860+ yield module
1861+
1862+
1863+def execd_submodule_paths(command, execd_dir=None):
1864+ """Generate a list of full paths to the specified command within exec_dir.
1865+ """
1866+ for module_path in execd_module_paths(execd_dir):
1867+ path = os.path.join(module_path, command)
1868+ if os.access(path, os.X_OK) and os.path.isfile(path):
1869+ yield path
1870+
1871+
1872+def execd_sentinel_path(submodule_path):
1873+ module_path = os.path.dirname(submodule_path)
1874+ execd_path = os.path.dirname(module_path)
1875+ module_name = os.path.basename(module_path)
1876+ submodule_name = os.path.basename(submodule_path)
1877+ return os.path.join(execd_path,
1878+ '.{}_{}.done'.format(module_name, submodule_name))
1879+
1880+
1881+def execd_run(command, execd_dir=None, stop_on_error=True, stderr=None):
1882+ """Run command for each module within execd_dir which defines it."""
1883+ if stderr is None:
1884+ stderr = sys.stdout
1885+ for submodule_path in execd_submodule_paths(command, execd_dir):
1886+ # Only run each execd once. We cannot simply run them in the
1887+ # install hook, as potentially storage hooks are run before that.
1888+ # We cannot rely on them being idempotent.
1889+ sentinel = execd_sentinel_path(submodule_path)
1890+ if os.path.exists(sentinel):
1891+ continue
1892+
1893+ try:
1894+ subprocess.check_call([submodule_path], stderr=stderr,
1895+ universal_newlines=True)
1896+ with open(sentinel, 'w') as f:
1897+ f.write('{} ran successfully {}\n'.format(submodule_path,
1898+ time.ctime()))
1899+ f.write('Removing this file will cause it to be run again\n')
1900+ except subprocess.CalledProcessError as e:
1901+ # Logs get the details. We can't use juju-log, as the
1902+ # output may be substantial and exceed command line
1903+ # length limits.
1904+ print("ERROR ({}) running {}".format(e.returncode, e.cmd),
1905+ file=stderr)
1906+ print("STDOUT<<EOM", file=stderr)
1907+ print(e.output, file=stderr)
1908+ print("EOM", file=stderr)
1909+
1910+ # Unit workload status gets a shorter fail message.
1911+ short_path = os.path.relpath(submodule_path)
1912+ block_msg = "Error ({}) running {}".format(e.returncode,
1913+ short_path)
1914+ try:
1915+ subprocess.check_call(['status-set', 'blocked', block_msg],
1916+ universal_newlines=True)
1917+ if stop_on_error:
1918+ sys.exit(0) # Leave unit in blocked state.
1919+ except Exception:
1920+ pass # We care about the exec.d/* failure, not status-set.
1921+
1922+ if stop_on_error:
1923+ sys.exit(e.returncode or 1) # Error state for pre-1.24 Juju
1924+
1925+
1926+def execd_preinstall(execd_dir=None):
1927+ """Run charm-pre-install for each module within execd_dir."""
1928+ execd_run('charm-pre-install', execd_dir=execd_dir)
1929
1930=== added file 'deps/layer/layer-basic/metadata.yaml'
1931--- deps/layer/layer-basic/metadata.yaml 1970-01-01 00:00:00 +0000
1932+++ deps/layer/layer-basic/metadata.yaml 2017-02-15 08:51:47 +0000
1933@@ -0,0 +1,1 @@
1934+{}
1935
1936=== added directory 'deps/layer/layer-basic/reactive'
1937=== added file 'deps/layer/layer-basic/reactive/__init__.py'
1938=== added file 'deps/layer/layer-basic/requirements.txt'
1939--- deps/layer/layer-basic/requirements.txt 1970-01-01 00:00:00 +0000
1940+++ deps/layer/layer-basic/requirements.txt 2017-02-15 08:51:47 +0000
1941@@ -0,0 +1,2 @@
1942+flake8
1943+pytest
1944
1945=== added file 'deps/layer/layer-basic/tox.ini'
1946--- deps/layer/layer-basic/tox.ini 1970-01-01 00:00:00 +0000
1947+++ deps/layer/layer-basic/tox.ini 2017-02-15 08:51:47 +0000
1948@@ -0,0 +1,12 @@
1949+[tox]
1950+skipsdist=True
1951+envlist = py34, py35
1952+skip_missing_interpreters = True
1953+
1954+[testenv]
1955+commands = py.test -v
1956+deps =
1957+ -r{toxinidir}/requirements.txt
1958+
1959+[flake8]
1960+exclude=docs
1961
1962=== added file 'deps/layer/layer-basic/wheelhouse.txt'
1963--- deps/layer/layer-basic/wheelhouse.txt 1970-01-01 00:00:00 +0000
1964+++ deps/layer/layer-basic/wheelhouse.txt 2017-02-15 08:51:47 +0000
1965@@ -0,0 +1,3 @@
1966+pip>=7.0.0,<8.2.0
1967+charmhelpers>=0.4.0,<1.0.0
1968+charms.reactive>=0.1.0,<2.0.0
1969
1970=== added directory 'deps/layer/layer-leadership'
1971=== added file 'deps/layer/layer-leadership/.gitignore'
1972--- deps/layer/layer-leadership/.gitignore 1970-01-01 00:00:00 +0000
1973+++ deps/layer/layer-leadership/.gitignore 2017-02-15 08:51:47 +0000
1974@@ -0,0 +1,2 @@
1975+*~
1976+*.pyc
1977
1978=== added file 'deps/layer/layer-leadership/README.md'
1979--- deps/layer/layer-leadership/README.md 1970-01-01 00:00:00 +0000
1980+++ deps/layer/layer-leadership/README.md 2017-02-15 08:51:47 +0000
1981@@ -0,0 +1,88 @@
1982+# Leadership Layer for Juju Charms
1983+
1984+The Leadership layer is for charm-tools and 'charm build', making it
1985+easier for layered charms to deal with Juju leadership.
1986+
1987+This layer will initialize charms.reactive states, allowing you to
1988+write handlers that will be activated by these states. It allows you
1989+to completely avoid writing leader-elected and leader-settings-changed
1990+hooks. As a simple example, these two handlers are all that is required
1991+to make the leader unit generate a password if it is not already set,
1992+and have the shared password stored in a file on all units:
1993+
1994+```python
1995+import charms.leadership
1996+from charmhelpers.core.host import pwgen
1997+
1998+
1999+@when('leadership.is_leader')
2000+@when_not('leadership.set.admin_password')
2001+def generate_secret():
2002+ charms.leadership.leader_set(admin_password=pwgen())
2003+
2004+
2005+@when('leadership.changed.admin_password')
2006+def store_secret():
2007+ write_file('/etc/foopass', leader_get('admin_password'))
2008+```
2009+
2010+
2011+## States
2012+
2013+The following states are set appropriately on startup, before any @hook
2014+decorated methods are invoked:
2015+
2016+* `leadership.is_leader`
2017+
2018+ This state is set when the unit is the leader. The unit will remain
2019+ the leader for the remainder of the hook, but may not be leader in
2020+ future hooks.
2021+
2022+* `leadership.set.{varname}`
2023+
2024+ This state is set for each leadership setting (ie. the
2025+ `leadership.set.foo` state will be set if the leader has set
2026+ the foo leadership setting to any value). It will remain
2027+ set for the remainder of the hook, unless the unit is the leader
2028+ and calls `reactive.leadership.leader_set()` and resets the value
2029+ to None.
2030+
2031+* `leadership.changed.{varname}`
2032+
2033+ This state is set for each leadership setting that has changed
2034+ since the last hook. It will remain set for the remainder of the
2035+ hook. It will not be set in the next hook, unless the leader has
2036+ changed the leadership setting yet again.
2037+
2038+* `leadership.changed`
2039+
2040+ One or more leadership settings has changed since the last hook.
2041+ This state will remain set for the remainder of the hook. It will
2042+ not be set in the next hook, unless the leader has made further
2043+ changes.
2044+
2045+
2046+## Methods
2047+
2048+The `charms.leadership` module exposes the `leader_set()` and
2049+`leader_get()` methods, which match the methods found in the
2050+`charmhelpers.core.hookenv` module. `reactive.leadership.leader_set()`
2051+should be used instead of the charmhelpers function to ensure that
2052+the reactive state is updated when the leadership settings are. If you
2053+do not do this, then you risk handlers waiting on these states to not
2054+be run on the leader (because when the leader changes settings, it
2055+triggers leader-settings-changed hooks on the follower units but
2056+no hooks on itself).
2057+
2058+
2059+## Support
2060+
2061+This layer is maintained on Launchpad by
2062+Stuart Bishop (stuart.bishop@canonical.com).
2063+
2064+Code is available using git at git+ssh://git.launchpad.net/layer-leadership.
2065+
2066+Bug reports can be made at https://bugs.launchpad.net/layer-leadership.
2067+
2068+Queries and comments can be made on the Juju mailing list, Juju IRC
2069+channels, or at https://answers.launchpad.net/layer-leadership.
2070
2071=== added file 'deps/layer/layer-leadership/copyright'
2072--- deps/layer/layer-leadership/copyright 1970-01-01 00:00:00 +0000
2073+++ deps/layer/layer-leadership/copyright 2017-02-15 08:51:47 +0000
2074@@ -0,0 +1,15 @@
2075+Copyright 2015-2016 Canonical Ltd.
2076+
2077+This file is part of the Leadership Layer for Juju.
2078+
2079+This program is free software: you can redistribute it and/or modify
2080+it under the terms of the GNU General Public License version 3, as
2081+published by the Free Software Foundation.
2082+
2083+This program is distributed in the hope that it will be useful, but
2084+WITHOUT ANY WARRANTY; without even the implied warranties of
2085+MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2086+PURPOSE. See the GNU General Public License for more details.
2087+
2088+You should have received a copy of the GNU General Public License
2089+along with this program. If not, see <http://www.gnu.org/licenses/>.
2090
2091=== added file 'deps/layer/layer-leadership/layer.yaml'
2092--- deps/layer/layer-leadership/layer.yaml 1970-01-01 00:00:00 +0000
2093+++ deps/layer/layer-leadership/layer.yaml 2017-02-15 08:51:47 +0000
2094@@ -0,0 +1,17 @@
2095+# Copyright 2015-2016 Canonical Ltd.
2096+#
2097+# This file is part of the Leadership Layer for Juju.
2098+#
2099+# This program is free software: you can redistribute it and/or modify
2100+# it under the terms of the GNU General Public License version 3, as
2101+# published by the Free Software Foundation.
2102+#
2103+# This program is distributed in the hope that it will be useful, but
2104+# WITHOUT ANY WARRANTY; without even the implied warranties of
2105+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2106+# PURPOSE. See the GNU General Public License for more details.
2107+#
2108+# You should have received a copy of the GNU General Public License
2109+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2110+includes:
2111+ - layer:basic
2112
2113=== added directory 'deps/layer/layer-leadership/lib'
2114=== added directory 'deps/layer/layer-leadership/lib/charms'
2115=== added file 'deps/layer/layer-leadership/lib/charms/__init__.py'
2116--- deps/layer/layer-leadership/lib/charms/__init__.py 1970-01-01 00:00:00 +0000
2117+++ deps/layer/layer-leadership/lib/charms/__init__.py 2017-02-15 08:51:47 +0000
2118@@ -0,0 +1,2 @@
2119+from pkgutil import extend_path
2120+__path__ = extend_path(__path__, __name__)
2121
2122=== added file 'deps/layer/layer-leadership/lib/charms/leadership.py'
2123--- deps/layer/layer-leadership/lib/charms/leadership.py 1970-01-01 00:00:00 +0000
2124+++ deps/layer/layer-leadership/lib/charms/leadership.py 2017-02-15 08:51:47 +0000
2125@@ -0,0 +1,58 @@
2126+# Copyright 2015-2016 Canonical Ltd.
2127+#
2128+# This file is part of the Leadership Layer for Juju.
2129+#
2130+# This program is free software: you can redistribute it and/or modify
2131+# it under the terms of the GNU General Public License version 3, as
2132+# published by the Free Software Foundation.
2133+#
2134+# This program is distributed in the hope that it will be useful, but
2135+# WITHOUT ANY WARRANTY; without even the implied warranties of
2136+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2137+# PURPOSE. See the GNU General Public License for more details.
2138+#
2139+# You should have received a copy of the GNU General Public License
2140+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2141+
2142+from charmhelpers.core import hookenv
2143+from charmhelpers.core import unitdata
2144+
2145+from charms import reactive
2146+from charms.reactive import not_unless
2147+
2148+
2149+__all__ = ['leader_get', 'leader_set']
2150+
2151+
2152+@not_unless('leadership.is_leader')
2153+def leader_set(settings=None, **kw):
2154+ '''Change leadership settings, per charmhelpers.core.hookenv.leader_set.
2155+
2156+ The leadership.set.{key} reactive state will be set while the
2157+ leadership hook environment setting remains set.
2158+
2159+ Changed leadership settings will set the leadership.changed.{key}
2160+ and leadership.changed states. These states will remain set until
2161+ the following hook.
2162+
2163+ These state changes take effect immediately on the leader, and
2164+ in future hooks run on non-leaders. In this way both leaders and
2165+ non-leaders can share handlers, waiting on these states.
2166+ '''
2167+ settings = settings or {}
2168+ settings.update(kw)
2169+ previous = unitdata.kv().getrange('leadership.settings.', strip=True)
2170+
2171+ for key, value in settings.items():
2172+ if value != previous.get(key):
2173+ reactive.set_state('leadership.changed.{}'.format(key))
2174+ reactive.set_state('leadership.changed')
2175+ reactive.helpers.toggle_state('leadership.set.{}'.format(key),
2176+ value is not None)
2177+ hookenv.leader_set(settings)
2178+ unitdata.kv().update(settings, prefix='leadership.settings.')
2179+
2180+
2181+def leader_get(attribute=None):
2182+ '''Return leadership settings, per charmhelpers.core.hookenv.leader_get.'''
2183+ return hookenv.leader_get(attribute)
2184
2185=== added directory 'deps/layer/layer-leadership/reactive'
2186=== added file 'deps/layer/layer-leadership/reactive/__init__.py'
2187=== added file 'deps/layer/layer-leadership/reactive/leadership.py'
2188--- deps/layer/layer-leadership/reactive/leadership.py 1970-01-01 00:00:00 +0000
2189+++ deps/layer/layer-leadership/reactive/leadership.py 2017-02-15 08:51:47 +0000
2190@@ -0,0 +1,68 @@
2191+# Copyright 2015-2016 Canonical Ltd.
2192+#
2193+# This file is part of the Leadership Layer for Juju.
2194+#
2195+# This program is free software: you can redistribute it and/or modify
2196+# it under the terms of the GNU General Public License version 3, as
2197+# published by the Free Software Foundation.
2198+#
2199+# This program is distributed in the hope that it will be useful, but
2200+# WITHOUT ANY WARRANTY; without even the implied warranties of
2201+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2202+# PURPOSE. See the GNU General Public License for more details.
2203+#
2204+# You should have received a copy of the GNU General Public License
2205+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2206+
2207+from charmhelpers.core import hookenv
2208+from charmhelpers.core import unitdata
2209+
2210+from charms import reactive
2211+from charms.leadership import leader_get, leader_set
2212+
2213+
2214+__all__ = ['leader_get', 'leader_set'] # Backwards compatibility
2215+
2216+
2217+def initialize_leadership_state():
2218+ '''Initialize leadership.* states from the hook environment.
2219+
2220+ Invoked by hookenv.atstart() so states are available in
2221+ @hook decorated handlers.
2222+ '''
2223+ is_leader = hookenv.is_leader()
2224+ if is_leader:
2225+ hookenv.log('Initializing Leadership Layer (is leader)')
2226+ else:
2227+ hookenv.log('Initializing Leadership Layer (is follower)')
2228+
2229+ reactive.helpers.toggle_state('leadership.is_leader', is_leader)
2230+
2231+ previous = unitdata.kv().getrange('leadership.settings.', strip=True)
2232+ current = hookenv.leader_get()
2233+
2234+ # Handle deletions.
2235+ for key in set(previous.keys()) - set(current.keys()):
2236+ current[key] = None
2237+
2238+ any_changed = False
2239+ for key, value in current.items():
2240+ reactive.helpers.toggle_state('leadership.changed.{}'.format(key),
2241+ value != previous.get(key))
2242+ if value != previous.get(key):
2243+ any_changed = True
2244+ reactive.helpers.toggle_state('leadership.set.{}'.format(key),
2245+ value is not None)
2246+ reactive.helpers.toggle_state('leadership.changed', any_changed)
2247+
2248+ unitdata.kv().update(current, prefix='leadership.settings.')
2249+
2250+
2251+# Per https://github.com/juju-solutions/charms.reactive/issues/33,
2252+# this module may be imported multiple times so ensure the
2253+# initialization hook is only registered once. I have to piggy back
2254+# onto the namespace of a module imported before reactive discovery
2255+# to do this.
2256+if not hasattr(reactive, '_leadership_registered'):
2257+ hookenv.atstart(initialize_leadership_state)
2258+ reactive._leadership_registered = True
2259
2260=== added directory 'deps/layer/trunk'
2261=== added file 'deps/layer/trunk/README.md'
2262--- deps/layer/trunk/README.md 1970-01-01 00:00:00 +0000
2263+++ deps/layer/trunk/README.md 2017-02-15 08:51:47 +0000
2264@@ -0,0 +1,57 @@
2265+# IBM base layer
2266+
2267+The IBM base layer is a common starting point for IBM related charms. Use this
2268+layer if you are building a layered charm with IBM software.
2269+
2270+## Usage
2271+
2272+This charm is a base layer and not meant to be built as a charm on its own.
2273+
2274+To use this layer, simply include the following in the runtime charm's
2275+layer.yaml file
2276+
2277+```yaml
2278+includes: ['layer:ibm-base']
2279+```
2280+
2281+## Configuration
2282+
2283+**curl_url** - Location of the IBM product installation file(s). This should be a
2284+URL that curl can use to download files. Multiple URLs should be separated
2285+by a space.
2286+
2287+**NOTE**: cryptographic verification is required and must be
2288+specified as part of the URL query string with the key a valid hash
2289+algorithms md5, sha256, or sha512, and the the checksum value itself
2290+(`http://<url>?[md5|sha256|sha512]=<checksum>`).
2291+
2292+For example:
2293+
2294+* `http://example.com/file.tgz?sha256=<sum>`
2295+* `"sftp://example.com/file1.tgz?md5=<sum> ftp://example.com/file2.tgz?md5=<sum>"`
2296+
2297+**curl_options** - The options passed to the 'curl' command when fetching files
2298+from curl_url.
2299+
2300+For example:
2301+
2302+* `"-u <user:password>"`
2303+
2304+**license_accepted** - Some IBM charms require acceptance of a license before
2305+installation can proceed. If required, setting this option to True indicates
2306+that you have read and accepted the IBM terms and conditions found in the
2307+license file referenced by the charm.
2308+
2309+## States
2310+
2311+**ibm-base.curl.resource.fetched** - When this state is set the IBM base layer has
2312+downloaded and verified the resources configured by curl_url. If the charm
2313+does not require any curl resources this state can be ignored.
2314+
2315+**ibm-base.license.accepted** - When this state is set the user has signified
2316+their acceptance of the license found in the charm. If the charm implements
2317+the Juju 'terms' feature this state can be safely ignored.
2318+
2319+## Contacts
2320+
2321+IBM Juju Support Team <jujusupp@us.ibm.com>
2322
2323=== added file 'deps/layer/trunk/config.yaml'
2324--- deps/layer/trunk/config.yaml 1970-01-01 00:00:00 +0000
2325+++ deps/layer/trunk/config.yaml 2017-02-15 08:51:47 +0000
2326@@ -0,0 +1,29 @@
2327+options:
2328+ curl_url:
2329+ type: string
2330+ default: ''
2331+ description: |
2332+ Location of the IBM product installation file(s). This should be a URL
2333+ that curl can use to download files. Multiple URLs should be separated
2334+ by a space. NOTE: cryptographic verification is required and must be
2335+ specified as part of the URL query string with the key a valid hash
2336+ algorithms md5, sha256, or sha512, and the the checksum value itself
2337+ (http://<url>?[md5|sha256|sha512]=<checksum>).
2338+ For example:
2339+ 'http://example.com/file.tgz?sha256=<sum>'
2340+ 'sftp://example.com/file1.tgz?md5=<sum> ftp://example.com/file2.tgz?md5=<sum>'
2341+ curl_opts:
2342+ type: string
2343+ default: ''
2344+ description: |
2345+ The options passed to the 'curl' command when fetching files from
2346+ curl_url. For example:
2347+ '-u <user:password>'
2348+ license_accepted:
2349+ type: boolean
2350+ default: False
2351+ description: |
2352+ Some IBM charms require acceptance of a license before installation
2353+ can proceed. If required, setting this option to True indicates that you
2354+ have read and accepted the IBM terms and conditions found in the license
2355+ file referenced by the charm.
2356
2357=== added file 'deps/layer/trunk/layer.yaml'
2358--- deps/layer/trunk/layer.yaml 1970-01-01 00:00:00 +0000
2359+++ deps/layer/trunk/layer.yaml 2017-02-15 08:51:47 +0000
2360@@ -0,0 +1,9 @@
2361+includes:
2362+ - 'layer:basic'
2363+ - 'layer:apt'
2364+ - 'layer:leadership'
2365+options:
2366+ basic:
2367+ # Setting options for the basic layer.
2368+ packages:
2369+ - curl
2370
2371=== added file 'deps/layer/trunk/metadata.yaml'
2372--- deps/layer/trunk/metadata.yaml 1970-01-01 00:00:00 +0000
2373+++ deps/layer/trunk/metadata.yaml 2017-02-15 08:51:47 +0000
2374@@ -0,0 +1,15 @@
2375+name: ibm-base
2376+summary: This layer provides a common base for IBM charms to build off of.
2377+maintainer: IBM Juju Support Team <jujusupp@us.ibm.com>
2378+description: |
2379+ This layer provides license acceptance using the terms feature, it also
2380+ provides apt support from the apt layer, and Juju leadership from the
2381+ leadership layer.
2382+min-juju-version: '2.0-beta1'
2383+series:
2384+ - trusty
2385+ - xenial
2386+tags:
2387+ - ibm
2388+ - apt
2389+ - leadership
2390
2391=== added directory 'deps/layer/trunk/reactive'
2392=== added file 'deps/layer/trunk/reactive/ibm-base.sh'
2393--- deps/layer/trunk/reactive/ibm-base.sh 1970-01-01 00:00:00 +0000
2394+++ deps/layer/trunk/reactive/ibm-base.sh 2017-02-15 08:51:47 +0000
2395@@ -0,0 +1,107 @@
2396+#!/bin/bash
2397+source charms.reactive.sh
2398+set -e
2399+
2400+
2401+# Utility function to verify a downloaded resource
2402+# :param: file name
2403+# :param: checksum type
2404+# :param: checksum value
2405+function verify_curl_resource() {
2406+ local FILE=$1
2407+ local TYPE=$2
2408+ local EXPECTED_SUM=$3
2409+ local CALCULATED_SUM=""
2410+ local PROG=""
2411+
2412+ if [ ! -r ${FILE} ]; then
2413+ status-set blocked "ibm-base: could not read ${FILE}"
2414+ juju-log "Could not verify the downloaded resource. File could not be read: ${FILE}"
2415+ fi
2416+
2417+ # Set our checksum utility based on the requested type
2418+ case "${TYPE}" in
2419+ md5)
2420+ PROG='md5sum'
2421+ ;;
2422+ sha256)
2423+ PROG='sha256sum'
2424+ ;;
2425+ sha512)
2426+ PROG='sha512sum'
2427+ ;;
2428+ *)
2429+ status-set blocked "ibm-base: checksum type must be md5, sha215, or sha512"
2430+ juju-log "Could not verify the downloaded resource ${FILE}. Unknown checksum type: ${TYPE}"
2431+ return 1
2432+ esac
2433+
2434+ CALCULATED_SUM=`${PROG} ${FILE} | awk '{print $1}'`
2435+ if [ "${CALCULATED_SUM}" = "${EXPECTED_SUM}" ]; then
2436+ juju-log "Checksum verified for ${FILE}."
2437+ return 0
2438+ else
2439+ status-set blocked "ibm-base: checksums did not match"
2440+ juju-log "Checksum mismatch for ${FILE}. Expected ${EXPECTED_SUM}, got ${CALCULATED_SUM}"
2441+ return 1
2442+ fi
2443+}
2444+
2445+
2446+# Fetch curl resources if a URL is configured
2447+@when 'config.set.curl_url'
2448+@when_any 'config.new.curl_url' 'config.changed.curl_url' 'config.new.curl_opts' 'config.changed.curl_opts'
2449+function fetch_curl_resource() {
2450+ local ARCHIVE_DIR="${CHARM_DIR}/files/archives"
2451+ local CURL_URL=$(config-get 'curl_url')
2452+ local CURL_OPTS=$(config-get 'curl_opts')
2453+
2454+ status-set maintenance "ibm-base: fetching resource(s)"
2455+
2456+ mkdir -p ${ARCHIVE_DIR}
2457+ cd ${ARCHIVE_DIR}
2458+ # Multiple URLs may be separated by a space, so loop.
2459+ for URL_STRING in ${CURL_URL}
2460+ do
2461+ # For each URL_STRING, set the url, checksum type, and checksum value.
2462+ local URL=${URL_STRING%%\?*} # string before the first '?'
2463+ local FILE_NAME=${URL##*\/} # string after the last '/'
2464+ local SUM_STRING=${URL_STRING#*\?} # string after the first '?'
2465+ local SUM_TYPE=${SUM_STRING%%\=*} # string before the first '='
2466+ local SUM_VALUE=${SUM_STRING#*\=} # string after the first '='
2467+
2468+ if [ -z ${FILE_NAME} ]; then
2469+ FILE_NAME="juju-${RANDOM}"
2470+ fi
2471+ curl --silent --show-error ${CURL_OPTS} -o ${FILE_NAME} ${URL}
2472+
2473+ # Verify our resource checksum. If this fails, let verify_resource log
2474+ # the reason and exit successfully. Exiting non-zero would fail the hook,
2475+ # so return 0 and simply inform the user that verification failed.
2476+ verify_curl_resource ${FILE_NAME} ${SUM_TYPE} ${SUM_VALUE} || return 0
2477+ done
2478+ cd -
2479+
2480+ set_state 'ibm-base.curl.resource.fetched'
2481+ status-set active "ibm-base: curl resource(s) fetched"
2482+ juju-log 'Curl resource fetched'
2483+}
2484+
2485+
2486+# Handle license acceptance
2487+@when 'config.changed.license_accepted'
2488+function check_license_acceptance() {
2489+ local LIC_ACCEPTED=$(config-get 'license_accepted')
2490+
2491+ # compare lowercase LIC_ACCEPTED (requires bash > 4)
2492+ if [ "${LIC_ACCEPTED,,}" = "true" ]; then
2493+ set_state 'ibm-base.license.accepted'
2494+ juju-log 'License accepted'
2495+ else
2496+ remove_state 'ibm-base.license.accepted'
2497+ juju-log 'License NOT accepted'
2498+ fi
2499+}
2500+
2501+# Main reactive entry point
2502+reactive_handler_main
2503
2504=== added directory 'hooks'
2505=== removed directory 'hooks'
2506=== added file 'hooks/config-changed'
2507--- hooks/config-changed 1970-01-01 00:00:00 +0000
2508+++ hooks/config-changed 2017-02-15 08:51:47 +0000
2509@@ -0,0 +1,19 @@
2510+#!/usr/bin/env python3
2511+
2512+# Load modules from $CHARM_DIR/lib
2513+import sys
2514+sys.path.append('lib')
2515+
2516+from charms.layer import basic
2517+basic.bootstrap_charm_deps()
2518+basic.init_config_states()
2519+
2520+
2521+# This will load and run the appropriate @hook and other decorated
2522+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
2523+# and $CHARM_DIR/hooks/relations.
2524+#
2525+# See https://jujucharms.com/docs/stable/authors-charm-building
2526+# for more information on this pattern.
2527+from charms.reactive import main
2528+main()
2529
2530=== removed file 'hooks/config-changed'
2531--- hooks/config-changed 2015-06-10 10:01:41 +0000
2532+++ hooks/config-changed 1970-01-01 00:00:00 +0000
2533@@ -1,242 +0,0 @@
2534-#!/bin/bash
2535-
2536-set -e
2537-MQ_INSTALL_PATH=/opt/mqm
2538-ARCHITECTURE=`uname -m`
2539-RPM_INSTALL_ARG="-ivh --nodeps --force-debian"
2540-if [ "$ARCHITECTURE" = "ppc64le" ]; then
2541- RPM_INSTALL_ARG="-ivh --nodeps --force-debian --ignorearch"
2542-fi
2543-
2544-
2545-# Check whether MQ is installed
2546-is_mq_installed()
2547-{
2548- if [ -d $MQ_INSTALL_PATH/bin ]; then
2549- source $MQ_INSTALL_PATH/bin/setmqenv -s
2550- if [ $? == 0 ]; then
2551- source $MQ_INSTALL_PATH/bin/setmqenv -s
2552- echo "True"
2553- fi
2554- else
2555- echo "False"
2556- fi
2557-}
2558-
2559-# Remove MQ, if installed
2560-remove_software()
2561-{
2562-
2563- mq_installed=`is_mq_installed`
2564- if [ $mq_installed == True ]; then
2565- juju-log "Removing IBM MQ software."
2566-
2567- # Stop all queues
2568- questr="QMNAME("
2569- for queue in `$MQ_INSTALL_PATH/bin/dspmq -o installation | cut -d' ' -f1 `;
2570- do
2571- # Get the queue manager name
2572- queue_mgr_name=${queue:${#questr}:${#queue}-${#questr}-1}
2573- juju-log "Stopping queue manager $queue_mgr_name"
2574- set +e
2575- su -l mqm -c "$MQ_INSTALL_PATH/bin/endmqm -w $queue_mgr_name"
2576- su -l mqm -c "$MQ_INSTALL_PATH/bin/endmqlsr -w -m $queue_mgr_name"
2577- set -e
2578- done
2579- # Get list of packages and remove them
2580- mq_rpms="`rpm -qa | grep MQSeries`"
2581- juju-log "Removing package(s) $mq_rpms"
2582- rpm -ev --force-debian $mq_rpms
2583- fi
2584-
2585-}
2586-
2587-# Remove MQ if license not accpeted
2588-remove_unaccepted_software()
2589-{
2590-
2591- if [ $1 == False ]; then
2592- juju-log "Removing IBM MQ (if installed) as the license agreement is not accepted."
2593- remove_software
2594- fi
2595-
2596-}
2597-
2598-# Update system configuration after installing MQ
2599-configure_system()
2600-{
2601- juju-log "IBM MQ: Updating system configuration."
2602-
2603- # Some containers do not allow system updates.
2604- # Prevent the script from failing in such cases
2605- set +e
2606- shmmax=`sysctl kernel.shmmax | cut -d'=' -f2`
2607- if [ $shmmax -lt 268435456 ];
2608- then
2609- sysctl -w kernel.shmmax=268435456
2610- fi
2611-
2612- fmax=`sysctl fs.file-max | cut -d'=' -f2`
2613- if [ $fmax -lt 524288 ];
2614- then
2615- sysctl -w fs.file-max=524288
2616- fi
2617- sysctl -p
2618-
2619- # Add user ubuntu to mqm group
2620- adduser ubuntu mqm
2621-
2622- # Update mqm file limits
2623- if [ "`grep mqm /etc/security/limits.conf`"=="" ]; then
2624- sed -i 's/# End of file/mqm hard nofile 10240\nmqm soft nofile 10240\n# End of file/' /etc/security/limits.conf
2625- fi
2626- set -e
2627- juju-log "IBM MQ: Completed system configuration update."
2628-
2629-}
2630-
2631-# Verify installation
2632-verify_install()
2633-{
2634-
2635- juju-log "IBM MQ: Verifying installation."
2636-
2637- # Prevent the script from failing on failure.
2638- # It could because configure_system call failed
2639- set +e
2640-
2641- # Run as mqm user as root will not work
2642-
2643- # Create queue manager
2644- juju-log "IBM MQ: Create queue manager QMA."
2645- su -l mqm -c "$MQ_INSTALL_PATH/bin/crtmqm QMA"
2646- if [ $? == 0 ]; then
2647- juju-log "IBM MQ: queue manager QMA created."
2648- else
2649- juju-log "IBM MQ: Failed to create queue manager QMA."
2650- exit 0
2651- fi
2652-
2653- # Start queue manager
2654- juju-log "IBM MQ: Starting queue manager QMA."
2655- su -l mqm -c "$MQ_INSTALL_PATH/bin/strmqm QMA"
2656- if [ $? == 0 ]; then
2657- juju-log "IBM MQ: Queue manager QMA started."
2658- else
2659- juju-log "IBM MQ: Failed to start queue manager QMA."
2660- exit 0
2661- fi
2662-
2663- # Create queue
2664- juju-log "IBM MQ: Creating queue."
2665- su -l mqm -c "$MQ_INSTALL_PATH/bin/runmqsc QMA < $CHARM_DIR/files/archives/mq_create_queue.mqsc"
2666- if [ $? == 0 ]; then
2667- juju-log "IBM MQ: Queue created."
2668- else
2669- juju-log "IBM MQ: Failed to create queue."
2670- exit 0
2671- fi
2672-
2673- # Send message to queue
2674- juju-log "IBM MQ: Sending message to queue."
2675- su -l mqm -c "$MQ_INSTALL_PATH/samp/bin/amqsput QUEUE1 QMA < $CHARM_DIR/files/archives/qinput"
2676- if [ $? == 0 ]; then
2677- juju-log "IBM MQ: Message sent to queue."
2678- else
2679- juju-log "IBM MQ: Failed to send message to queue."
2680- exit 0
2681- fi
2682-
2683- # Give MQ some time
2684- sleep 5
2685-
2686- # Receive message from queue
2687- juju-log "IBM MQ: Receiving message from queue."
2688- su -l mqm -c "$MQ_INSTALL_PATH/samp/bin/amqsget QUEUE1 QMA"
2689- if [ $? == 0 ]; then
2690- juju-log "IBM MQ: Message received from queue."
2691- else
2692- juju-log "IBM MQ: Failed to receive message from queue."
2693- exit 0
2694- fi
2695-
2696- # Clean up
2697- # Delete queue
2698- juju-log "IBM MQ: Deleting queue."
2699- su -l mqm -c "$MQ_INSTALL_PATH/bin/runmqsc QMA < $CHARM_DIR/files/archives/mq_delete_queue.mqsc"
2700- if [ $? == 0 ]; then
2701- juju-log "IBM MQ: Deleted queue."
2702- else
2703- juju-log "IBM IB: Failed to delete queue."
2704- fi
2705-
2706- # End queue manger
2707- juju-log "IBM MQ: Stopping queue manager."
2708- su -l mqm -c "$MQ_INSTALL_PATH/bin/endmqm QMA"
2709- su -l mqm -c "$MQ_INSTALL_PATH/bin/endmqlsr -w -m QMA"
2710- if [ $? -eq 0 ]; then
2711- juju-log "IBM IB: Queue Manager is stopped."
2712- else
2713- juju-log "IBM IB: Queue Manager failed to stop."
2714- fi
2715-
2716- sleep 10
2717-
2718- juju-log "IBM MQ: Deleting queue manager."
2719- su -l mqm -c "$MQ_INSTALL_PATH/bin/dltmqm QMA"
2720- if [ $? -eq 0 ]; then
2721- juju-log "IBM IB: Queue Manager is deleted."
2722- else
2723- juju-log "IBM IB: Queue Manager could not be deleted."
2724- fi
2725-
2726- set -e
2727- juju-log "IBM MQ: Install verification completed."
2728-
2729-}
2730-
2731-
2732-juju-log "IBM MQ: Begin config-change hook"
2733-mq_license_accepted=`config-get accept-ibm-mq-license`
2734-
2735-
2736-# Remove MQ if license not accepted and exit. Else install it
2737-remove_unaccepted_software $mq_license_accepted
2738-if [ $mq_license_accepted == False ]; then
2739- juju-log "IBM MQ License not accepted."
2740-
2741-elif [ $mq_license_accepted == True ]; then
2742-
2743- juju-log "License accepted"
2744- mq_inst=`is_mq_installed`
2745- if [ -f $CHARM_DIR/files/archives/*.gz ]; then
2746- juju-log "IBM MQ: Extracting IBM MQ package."
2747- tar xvfz $CHARM_DIR/files/archives/*.gz --strip-components=1 -C $CHARM_DIR/files/archives
2748- rm -rf $CHARM_DIR/files/archives/*.gz
2749- juju-log "IBM MQ: Extracted IBM MQ package."
2750- juju-log "Awaiting acceptance of IBM MQ license (see README on how to accept the license)."
2751- fi
2752- if [ $mq_inst == False ]; then
2753- # Check MQ package availability
2754- if [ -f $CHARM_DIR/files/archives/MQSeriesServer*rpm ] && [ -f $CHARM_DIR/files/archives/MQSeriesRuntime*rpm ];
2755- then
2756- echo "MQ Packages available for installation.";
2757- $CHARM_DIR/files/archives/mqlicense.sh -accept
2758- juju-log "Installing available MQ packages."
2759- rpm $RPM_INSTALL_ARG --prefix $MQ_INSTALL_PATH $CHARM_DIR/files/archives/MQSeries*rpm
2760- juju-log "Installation of available MQ packages complete."
2761- # Configure system values for MQ
2762- configure_system
2763- #Verify installation
2764- verify_install
2765- else
2766- echo "MQ Packages missing. Please check README file.";
2767- echo "Upgrade MQ charm after adding the MQ packages";
2768- exit 0
2769- fi
2770- fi
2771-else
2772- juju-log " Acceptable values for license is 'True' or 'False'"
2773-
2774-fi
2775-juju-log "IBM MQ: End config-change hook"
2776
2777=== added file 'hooks/hook.template'
2778--- hooks/hook.template 1970-01-01 00:00:00 +0000
2779+++ hooks/hook.template 2017-02-15 08:51:47 +0000
2780@@ -0,0 +1,19 @@
2781+#!/usr/bin/env python3
2782+
2783+# Load modules from $CHARM_DIR/lib
2784+import sys
2785+sys.path.append('lib')
2786+
2787+from charms.layer import basic
2788+basic.bootstrap_charm_deps()
2789+basic.init_config_states()
2790+
2791+
2792+# This will load and run the appropriate @hook and other decorated
2793+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
2794+# and $CHARM_DIR/hooks/relations.
2795+#
2796+# See https://jujucharms.com/docs/stable/authors-charm-building
2797+# for more information on this pattern.
2798+from charms.reactive import main
2799+main()
2800
2801=== added file 'hooks/install'
2802--- hooks/install 1970-01-01 00:00:00 +0000
2803+++ hooks/install 2017-02-15 08:51:47 +0000
2804@@ -0,0 +1,19 @@
2805+#!/usr/bin/env python3
2806+
2807+# Load modules from $CHARM_DIR/lib
2808+import sys
2809+sys.path.append('lib')
2810+
2811+from charms.layer import basic
2812+basic.bootstrap_charm_deps()
2813+basic.init_config_states()
2814+
2815+
2816+# This will load and run the appropriate @hook and other decorated
2817+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
2818+# and $CHARM_DIR/hooks/relations.
2819+#
2820+# See https://jujucharms.com/docs/stable/authors-charm-building
2821+# for more information on this pattern.
2822+from charms.reactive import main
2823+main()
2824
2825=== removed file 'hooks/install'
2826--- hooks/install 2015-06-10 10:01:41 +0000
2827+++ hooks/install 1970-01-01 00:00:00 +0000
2828@@ -1,30 +0,0 @@
2829-#!/bin/bash
2830-set -e
2831-#Install RPM dependency
2832-juju-log "IBM MQ: Begin Install."
2833-
2834-# Get the architecture from the uname command.
2835-ARCHITECTURE=`uname -m`
2836-if [ "$ARCHITECTURE" != "x86_64" -a "$ARCHITECTURE" != "ppc64le" ]; then
2837- juju-log "IBM MQ: Unsupported platform. IBM MQ installed with this Charm supports only the x86_64 and POWER LE (ppc64le) platforms."
2838- exit 1
2839-fi
2840-
2841-# Install RPM
2842-juju-log "IBM MQ: Downloading and installng RPM."
2843-apt-get install -y rpm
2844-juju-log "IBM MQ: RPM downloaded and installed."
2845-
2846-# Extract IBM MQ archive file, if it exists to files/archives
2847-if [ -f $CHARM_DIR/files/archives/*.gz ]; then
2848- juju-log "IBM MQ: Extracting IBM MQ package."
2849- tar xvfz $CHARM_DIR/files/archives/*.gz --strip-components=1 -C $CHARM_DIR/files/archives
2850- rm -rf $CHARM_DIR/files/archives/*.gz
2851- juju-log "IBM MQ: Extracted IBM MQ package."
2852- juju-log "Awaiting acceptance of IBM MQ license (see README on how to accept the license)."
2853-else
2854- juju-log "IBM MQ: IBM MQ packages not available. Refer the README on how to add it. Deploy the charm again after adding the package."
2855-fi
2856-
2857-juju-log "IBM MQ: End Install."
2858-
2859
2860=== added file 'hooks/leader-elected'
2861--- hooks/leader-elected 1970-01-01 00:00:00 +0000
2862+++ hooks/leader-elected 2017-02-15 08:51:47 +0000
2863@@ -0,0 +1,19 @@
2864+#!/usr/bin/env python3
2865+
2866+# Load modules from $CHARM_DIR/lib
2867+import sys
2868+sys.path.append('lib')
2869+
2870+from charms.layer import basic
2871+basic.bootstrap_charm_deps()
2872+basic.init_config_states()
2873+
2874+
2875+# This will load and run the appropriate @hook and other decorated
2876+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
2877+# and $CHARM_DIR/hooks/relations.
2878+#
2879+# See https://jujucharms.com/docs/stable/authors-charm-building
2880+# for more information on this pattern.
2881+from charms.reactive import main
2882+main()
2883
2884=== added file 'hooks/leader-settings-changed'
2885--- hooks/leader-settings-changed 1970-01-01 00:00:00 +0000
2886+++ hooks/leader-settings-changed 2017-02-15 08:51:47 +0000
2887@@ -0,0 +1,19 @@
2888+#!/usr/bin/env python3
2889+
2890+# Load modules from $CHARM_DIR/lib
2891+import sys
2892+sys.path.append('lib')
2893+
2894+from charms.layer import basic
2895+basic.bootstrap_charm_deps()
2896+basic.init_config_states()
2897+
2898+
2899+# This will load and run the appropriate @hook and other decorated
2900+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
2901+# and $CHARM_DIR/hooks/relations.
2902+#
2903+# See https://jujucharms.com/docs/stable/authors-charm-building
2904+# for more information on this pattern.
2905+from charms.reactive import main
2906+main()
2907
2908=== added file 'hooks/messaging-relation-broken'
2909--- hooks/messaging-relation-broken 1970-01-01 00:00:00 +0000
2910+++ hooks/messaging-relation-broken 2017-02-15 08:51:47 +0000
2911@@ -0,0 +1,19 @@
2912+#!/usr/bin/env python3
2913+
2914+# Load modules from $CHARM_DIR/lib
2915+import sys
2916+sys.path.append('lib')
2917+
2918+from charms.layer import basic
2919+basic.bootstrap_charm_deps()
2920+basic.init_config_states()
2921+
2922+
2923+# This will load and run the appropriate @hook and other decorated
2924+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
2925+# and $CHARM_DIR/hooks/relations.
2926+#
2927+# See https://jujucharms.com/docs/stable/authors-charm-building
2928+# for more information on this pattern.
2929+from charms.reactive import main
2930+main()
2931
2932=== added file 'hooks/messaging-relation-changed'
2933--- hooks/messaging-relation-changed 1970-01-01 00:00:00 +0000
2934+++ hooks/messaging-relation-changed 2017-02-15 08:51:47 +0000
2935@@ -0,0 +1,19 @@
2936+#!/usr/bin/env python3
2937+
2938+# Load modules from $CHARM_DIR/lib
2939+import sys
2940+sys.path.append('lib')
2941+
2942+from charms.layer import basic
2943+basic.bootstrap_charm_deps()
2944+basic.init_config_states()
2945+
2946+
2947+# This will load and run the appropriate @hook and other decorated
2948+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
2949+# and $CHARM_DIR/hooks/relations.
2950+#
2951+# See https://jujucharms.com/docs/stable/authors-charm-building
2952+# for more information on this pattern.
2953+from charms.reactive import main
2954+main()
2955
2956=== added file 'hooks/messaging-relation-departed'
2957--- hooks/messaging-relation-departed 1970-01-01 00:00:00 +0000
2958+++ hooks/messaging-relation-departed 2017-02-15 08:51:47 +0000
2959@@ -0,0 +1,19 @@
2960+#!/usr/bin/env python3
2961+
2962+# Load modules from $CHARM_DIR/lib
2963+import sys
2964+sys.path.append('lib')
2965+
2966+from charms.layer import basic
2967+basic.bootstrap_charm_deps()
2968+basic.init_config_states()
2969+
2970+
2971+# This will load and run the appropriate @hook and other decorated
2972+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
2973+# and $CHARM_DIR/hooks/relations.
2974+#
2975+# See https://jujucharms.com/docs/stable/authors-charm-building
2976+# for more information on this pattern.
2977+from charms.reactive import main
2978+main()
2979
2980=== added file 'hooks/messaging-relation-joined'
2981--- hooks/messaging-relation-joined 1970-01-01 00:00:00 +0000
2982+++ hooks/messaging-relation-joined 2017-02-15 08:51:47 +0000
2983@@ -0,0 +1,19 @@
2984+#!/usr/bin/env python3
2985+
2986+# Load modules from $CHARM_DIR/lib
2987+import sys
2988+sys.path.append('lib')
2989+
2990+from charms.layer import basic
2991+basic.bootstrap_charm_deps()
2992+basic.init_config_states()
2993+
2994+
2995+# This will load and run the appropriate @hook and other decorated
2996+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
2997+# and $CHARM_DIR/hooks/relations.
2998+#
2999+# See https://jujucharms.com/docs/stable/authors-charm-building
3000+# for more information on this pattern.
3001+from charms.reactive import main
3002+main()
3003
3004=== removed file 'hooks/messaging-relation-joined'
3005--- hooks/messaging-relation-joined 2015-05-03 16:45:29 +0000
3006+++ hooks/messaging-relation-joined 1970-01-01 00:00:00 +0000
3007@@ -1,12 +0,0 @@
3008-#!/bin/bash
3009-
3010-juju-log "IBM MQ: Begin messaging-relation-joined hook."
3011-set -e
3012-mq_license_accepted=`config-get accept-ibm-mq-license`
3013-if [ "$mq_license_accepted" == "False" ]; then
3014- juju-log "IBM MQ: IBM MQ License is not accepted."
3015- juju-log "Delete the relation. Accept the IBM MQ License, as per the README, before setting up any relation."
3016- exit 0
3017-fi
3018-relation-set hostname=`unit-get private-address` port=1414
3019-juju-log "IBM MQ: End messaging-relation-joined hook."
3020
3021=== added directory 'hooks/relations'
3022=== added directory 'hooks/relations/ibm-mq'
3023=== added file 'hooks/relations/ibm-mq/README.md'
3024--- hooks/relations/ibm-mq/README.md 1970-01-01 00:00:00 +0000
3025+++ hooks/relations/ibm-mq/README.md 2017-02-15 08:51:47 +0000
3026@@ -0,0 +1,42 @@
3027+Overview
3028+-----------
3029+
3030+This interface layer handles the communication between `IBM MQ` and other charms that tries to communicate by sending each other data in messages through IBM-MQ rather than by calling each other directly.
3031+The provider end of this interface provides the Queue Manager name, Queue Name, IP Address and Port.
3032+The consumer/requires part will be any other charm and can get all the above values viz. Queue Manager name, Queue Name, IP Address and Port to send or receive messages. Here we are describing consumer charm as `ibm-was-base`
3033+
3034+
3035+Usage
3036+------
3037+
3038+#### Provides
3039+IBM MQ product will provide this interface. This interface layer will set the following states, as appropriate:
3040+
3041+ - `{relation_name}.connected`: The relation is established, IBM-MQ is ready to send it's information.
3042+
3043+ - `get_consumer_hostname()` - returns/gets the consumer hostname to create a channel authentication rule that allows client system to use the channel by entering the MQSC command.
3044+
3045+ - `set_mq_details()` - Sets Queue Manager name, Queue Name, IP Address and Port for the connected services.
3046+ As a Queue Name it sets a filename where MQSC Commands can be edited for further operation.
3047+
3048+
3049+ - `{relation_name}.departed` : The relation has been removed. Any cleanup related to the consumer charm (e.g IBM WebSphere Server) should happen now on the Consumer charm since the consumer is going away.
3050+
3051+#### Requires
3052+
3053+Consumer charms e.g `IBM WebSphere Application Server` will require this interface to connect to IBM-MQ so that they can get the necessary information about `IBM-MQ` to send/receive messages to other Applications.
3054+This interface layer will set the following states, as appropriate:
3055+
3056+- `{relation_name}.connected` : The consumer charm has been related to a IBM-MQ provider charm.
3057+ At this point, the charm waits for Provider charm to send details like Queue Manager name, Queue Name, IP Address and Port.
3058+
3059+ - `set_hostname()` - sets the `hostname`, so that IBM-MQ can get the consumer/client hostname to allow to send/receive messages.
3060+
3061+- `{relation_name}.ready` : The consumer charm e.g `IBM WebSphere Application Server` is now ready to access the details of IBM-MQ to send/recieve messages. Such as Queue Manager name, Queue Name, IP Address/hostname and Port.
3062+
3063+ - `get_qm_name()` - returns the `QM_Name` that IBM-MQ has created.
3064+ - `get_qname()` - returns the `QName` that IBM-MQ has created.
3065+ - `get_mq_hostname()` - returns the `hostname` that IBM-MQ has created.
3066+ - `get_mq_port()` - returns the `MQ Port` that IBM-MQ has created.
3067+
3068+- `{relation_name}.departed` : The relation has been removed. Any cleanup related to the provider charm should happen now.
3069
3070=== added file 'hooks/relations/ibm-mq/__init__.py'
3071=== added file 'hooks/relations/ibm-mq/interface.yaml'
3072--- hooks/relations/ibm-mq/interface.yaml 1970-01-01 00:00:00 +0000
3073+++ hooks/relations/ibm-mq/interface.yaml 2017-02-15 08:51:47 +0000
3074@@ -0,0 +1,4 @@
3075+name: ibm-mq
3076+summary: Interface for relating to ibm-mq
3077+version: 1
3078+maintainer: "Juju Support <jujusupp@us.ibm.com>"
3079\ No newline at end of file
3080
3081=== added file 'hooks/relations/ibm-mq/provides.py'
3082--- hooks/relations/ibm-mq/provides.py 1970-01-01 00:00:00 +0000
3083+++ hooks/relations/ibm-mq/provides.py 2017-02-15 08:51:47 +0000
3084@@ -0,0 +1,50 @@
3085+from charms.reactive import hook
3086+from charms.reactive import RelationBase
3087+from charms.reactive import scopes
3088+
3089+
3090+class mqProvides(RelationBase):
3091+ # Every unit connecting will get the same information
3092+ scope = scopes.SERVICE
3093+
3094+ @hook('{provides:ibm-mq}-relation-joined')
3095+ def joined(self):
3096+ conversation = self.conversation()
3097+ conversation.remove_state('{relation_name}.departed')
3098+ conversation.set_state('{relation_name}.connected')
3099+
3100+ @hook('{provides:ibm-mq}-relation-departed')
3101+ def departed(self):
3102+ conversation = self.conversation()
3103+ conversation.remove_state('{relation_name}.connected')
3104+ conversation.set_state('{relation_name}.departed')
3105+
3106+ def dismiss(self, service):
3107+ conversation = self.conversation(service)
3108+ conversation.remove_state('{relation_name}.departed')
3109+
3110+ def reset_states(self, service):
3111+ conversation = self.conversation(service)
3112+ conversation.remove_state('{relation_name}.connected')
3113+ conversation.remove_state('{relation_name}.departed')
3114+
3115+ def set_mq_details(self, service, QM_Name, Qname, host, port):
3116+ conversation = self.conversation(service)
3117+ conversation.set_remote(data={
3118+ 'QM_Name': QM_Name,
3119+ 'Qname': Qname,
3120+ 'host': host,
3121+ 'port': port,
3122+ })
3123+
3124+ def get_consumer_hostname(self):
3125+ return self.get_remote('host')
3126+
3127+ def services(self):
3128+ """
3129+ Return a list of services requesting MQ.
3130+ """
3131+ service = []
3132+ for conversation in self.conversations():
3133+ service.append(conversation.scope)
3134+ return service
3135
3136=== added file 'hooks/relations/ibm-mq/requires.py'
3137--- hooks/relations/ibm-mq/requires.py 1970-01-01 00:00:00 +0000
3138+++ hooks/relations/ibm-mq/requires.py 2017-02-15 08:51:47 +0000
3139@@ -0,0 +1,42 @@
3140+from charms.reactive import hook
3141+from charms.reactive import RelationBase
3142+from charms.reactive import scopes
3143+
3144+
3145+class mq1Requires(RelationBase):
3146+ scope = scopes.GLOBAL
3147+
3148+ @hook('{requires:ibm-mq}-relation-joined')
3149+ def joined(self):
3150+ self.remove_state('{relation_name}.departed')
3151+ self.set_state('{relation_name}.connected')
3152+
3153+ @hook('{requires:ibm-mq}-relation-changed')
3154+ def changed(self):
3155+ if str(self.get_remote('port')) != "None":
3156+ self.set_state('{relation_name}.ready')
3157+ print("Status is relation_name.ready in requires")
3158+
3159+ @hook('{requires:ibm-mq}-relation-departed')
3160+ def departed(self):
3161+ self.remove_state('{relation_name}.connected')
3162+ self.remove_state('{relation_name}.ready')
3163+ self.set_state('{relation_name}.departed')
3164+
3165+ def set_hostname(self, host):
3166+ conversation = self.conversation()
3167+ conversation.set_remote(data={
3168+ 'host': host,
3169+ })
3170+
3171+ def get_qm_name(self):
3172+ return self.get_remote('QM_Name')
3173+
3174+ def get_qname(self):
3175+ return self.get_remote('Qname')
3176+
3177+ def get_mq_hostname(self):
3178+ return self.get_remote('host')
3179+
3180+ def get_mq_port(self):
3181+ return self.get_remote('port')
3182
3183=== added file 'hooks/start'
3184--- hooks/start 1970-01-01 00:00:00 +0000
3185+++ hooks/start 2017-02-15 08:51:47 +0000
3186@@ -0,0 +1,19 @@
3187+#!/usr/bin/env python3
3188+
3189+# Load modules from $CHARM_DIR/lib
3190+import sys
3191+sys.path.append('lib')
3192+
3193+from charms.layer import basic
3194+basic.bootstrap_charm_deps()
3195+basic.init_config_states()
3196+
3197+
3198+# This will load and run the appropriate @hook and other decorated
3199+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
3200+# and $CHARM_DIR/hooks/relations.
3201+#
3202+# See https://jujucharms.com/docs/stable/authors-charm-building
3203+# for more information on this pattern.
3204+from charms.reactive import main
3205+main()
3206
3207=== removed file 'hooks/start'
3208--- hooks/start 2015-03-06 08:32:33 +0000
3209+++ hooks/start 1970-01-01 00:00:00 +0000
3210@@ -1,8 +0,0 @@
3211-#!/bin/bash
3212-
3213-# Juju command to open port
3214-
3215-set -e
3216-juju-log "IBM MQ: Begin Start hook"
3217-open-port 1414/TCP
3218-juju-log "IBM MQ: End Start hook"
3219
3220=== added file 'hooks/stop'
3221--- hooks/stop 1970-01-01 00:00:00 +0000
3222+++ hooks/stop 2017-02-15 08:51:47 +0000
3223@@ -0,0 +1,19 @@
3224+#!/usr/bin/env python3
3225+
3226+# Load modules from $CHARM_DIR/lib
3227+import sys
3228+sys.path.append('lib')
3229+
3230+from charms.layer import basic
3231+basic.bootstrap_charm_deps()
3232+basic.init_config_states()
3233+
3234+
3235+# This will load and run the appropriate @hook and other decorated
3236+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
3237+# and $CHARM_DIR/hooks/relations.
3238+#
3239+# See https://jujucharms.com/docs/stable/authors-charm-building
3240+# for more information on this pattern.
3241+from charms.reactive import main
3242+main()
3243
3244=== removed file 'hooks/stop'
3245--- hooks/stop 2015-03-06 08:32:33 +0000
3246+++ hooks/stop 1970-01-01 00:00:00 +0000
3247@@ -1,6 +0,0 @@
3248-#!/bin/bash
3249-
3250-set -e
3251-juju-log "IBM MQ: Begin Stop hook"
3252-close-port 1414/TCP
3253-juju-log "IBM MQ: End Stop hook"
3254
3255=== added file 'hooks/update-status'
3256--- hooks/update-status 1970-01-01 00:00:00 +0000
3257+++ hooks/update-status 2017-02-15 08:51:47 +0000
3258@@ -0,0 +1,19 @@
3259+#!/usr/bin/env python3
3260+
3261+# Load modules from $CHARM_DIR/lib
3262+import sys
3263+sys.path.append('lib')
3264+
3265+from charms.layer import basic
3266+basic.bootstrap_charm_deps()
3267+basic.init_config_states()
3268+
3269+
3270+# This will load and run the appropriate @hook and other decorated
3271+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
3272+# and $CHARM_DIR/hooks/relations.
3273+#
3274+# See https://jujucharms.com/docs/stable/authors-charm-building
3275+# for more information on this pattern.
3276+from charms.reactive import main
3277+main()
3278
3279=== added file 'hooks/upgrade-charm'
3280--- hooks/upgrade-charm 1970-01-01 00:00:00 +0000
3281+++ hooks/upgrade-charm 2017-02-15 08:51:47 +0000
3282@@ -0,0 +1,28 @@
3283+#!/usr/bin/env python3
3284+
3285+# Load modules from $CHARM_DIR/lib
3286+import os
3287+import sys
3288+sys.path.append('lib')
3289+
3290+# This is an upgrade-charm context, make sure we install latest deps
3291+if not os.path.exists('wheelhouse/.upgrade'):
3292+ open('wheelhouse/.upgrade', 'w').close()
3293+ if os.path.exists('wheelhouse/.bootstrapped'):
3294+ os.unlink('wheelhouse/.bootstrapped')
3295+else:
3296+ os.unlink('wheelhouse/.upgrade')
3297+
3298+from charms.layer import basic
3299+basic.bootstrap_charm_deps()
3300+basic.init_config_states()
3301+
3302+
3303+# This will load and run the appropriate @hook and other decorated
3304+# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
3305+# and $CHARM_DIR/hooks/relations.
3306+#
3307+# See https://jujucharms.com/docs/stable/authors-charm-building
3308+# for more information on this pattern.
3309+from charms.reactive import main
3310+main()
3311
3312=== added file 'layer.yaml'
3313--- layer.yaml 1970-01-01 00:00:00 +0000
3314+++ layer.yaml 2017-02-15 08:51:47 +0000
3315@@ -0,0 +1,21 @@
3316+"options":
3317+ "basic":
3318+ "packages":
3319+ - "curl"
3320+ - "rpm"
3321+ "use_venv": !!bool "false"
3322+ "include_system_packages": !!bool "false"
3323+ "ibm-base": {}
3324+ "leadership": {}
3325+ "ibm-mq": {}
3326+ "apt":
3327+ "packages": []
3328+"includes":
3329+- "layer:basic"
3330+- "layer:basic"
3331+- "layer:apt"
3332+- "layer:leadership"
3333+- "layer:ibm-base"
3334+- "interface:ibm-mq"
3335+"repo": "bzr+ssh://bazaar.launchpad.net/~ibmcharmers/charms/trusty/layer-ibm-mq/trunk/"
3336+"is": "ibm-mq"
3337
3338=== added directory 'lib'
3339=== added directory 'lib/charms'
3340=== added file 'lib/charms/__init__.py'
3341--- lib/charms/__init__.py 1970-01-01 00:00:00 +0000
3342+++ lib/charms/__init__.py 2017-02-15 08:51:47 +0000
3343@@ -0,0 +1,2 @@
3344+from pkgutil import extend_path
3345+__path__ = extend_path(__path__, __name__)
3346
3347=== added file 'lib/charms/apt.py'
3348--- lib/charms/apt.py 1970-01-01 00:00:00 +0000
3349+++ lib/charms/apt.py 2017-02-15 08:51:47 +0000
3350@@ -0,0 +1,182 @@
3351+# Copyright 2015-2016 Canonical Ltd.
3352+#
3353+# This file is part of the Apt layer for Juju.
3354+#
3355+# This program is free software: you can redistribute it and/or modify
3356+# it under the terms of the GNU General Public License version 3, as
3357+# published by the Free Software Foundation.
3358+#
3359+# This program is distributed in the hope that it will be useful, but
3360+# WITHOUT ANY WARRANTY; without even the implied warranties of
3361+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
3362+# PURPOSE. See the GNU General Public License for more details.
3363+#
3364+# You should have received a copy of the GNU General Public License
3365+# along with this program. If not, see <http://www.gnu.org/licenses/>.
3366+
3367+'''
3368+charms.reactive helpers for dealing with deb packages.
3369+
3370+Add apt package sources using add_source(). Queue deb packages for
3371+installation with install(). Configure and work with your software
3372+once the apt.installed.{packagename} state is set.
3373+'''
3374+import itertools
3375+import subprocess
3376+
3377+from charmhelpers import fetch
3378+from charmhelpers.core import hookenv, unitdata
3379+from charms import reactive
3380+
3381+
3382+__all__ = ['add_source', 'update', 'queue_install', 'install_queued',
3383+ 'installed', 'purge', 'ensure_package_status']
3384+
3385+
3386+def add_source(source, key=None):
3387+ '''Add an apt source.
3388+
3389+ Sets the apt.needs_update state.
3390+
3391+ A source may be either a line that can be added directly to
3392+ sources.list(5), or in the form ppa:<user>/<ppa-name> for adding
3393+ Personal Package Archives, or a distribution component to enable.
3394+
3395+ The package signing key should be an ASCII armoured GPG key. While
3396+ GPG key ids are also supported, the retrieval mechanism is insecure.
3397+ There is no need to specify the package signing key for PPAs or for
3398+ the main Ubuntu archives.
3399+ '''
3400+ # Maybe we should remember which sources have been added already
3401+ # so we don't waste time re-adding them. Is this time significant?
3402+ fetch.add_source(source, key)
3403+ reactive.set_state('apt.needs_update')
3404+
3405+
3406+def queue_install(packages, options=None):
3407+ """Queue one or more deb packages for install.
3408+
3409+ The `apt.installed.{name}` state is set once the package is installed.
3410+
3411+ If a package has already been installed it will not be reinstalled.
3412+
3413+ If a package has already been queued it will not be requeued, and
3414+ the install options will not be changed.
3415+
3416+ Sets the apt.queued_installs state.
3417+ """
3418+ if isinstance(packages, str):
3419+ packages = [packages]
3420+ # Filter installed packages.
3421+ store = unitdata.kv()
3422+ queued_packages = store.getrange('apt.install_queue.', strip=True)
3423+ packages = {package: options for package in packages
3424+ if not (package in queued_packages or
3425+ reactive.helpers.is_state('apt.installed.' + package))}
3426+ if packages:
3427+ unitdata.kv().update(packages, prefix='apt.install_queue.')
3428+ reactive.set_state('apt.queued_installs')
3429+
3430+
3431+def installed():
3432+ '''Return the set of deb packages completed install'''
3433+ return set(state.split('.', 2)[2] for state in reactive.bus.get_states()
3434+ if state.startswith('apt.installed.'))
3435+
3436+
3437+def purge(packages):
3438+ """Purge one or more deb packages from the system"""
3439+ fetch.apt_purge(packages, fatal=True)
3440+ store = unitdata.kv()
3441+ store.unsetrange(packages, prefix='apt.install_queue.')
3442+ for package in packages:
3443+ reactive.remove_state('apt.installed.{}'.format(package))
3444+
3445+
3446+def update():
3447+ """Update the apt cache.
3448+
3449+ Removes the apt.needs_update state.
3450+ """
3451+ status_set(None, 'Updating apt cache')
3452+ fetch.apt_update(fatal=True) # Friends don't let friends set fatal=False
3453+ reactive.remove_state('apt.needs_update')
3454+
3455+
3456+def install_queued():
3457+ '''Installs queued deb packages.
3458+
3459+ Removes the apt.queued_installs state and sets the apt.installed state.
3460+
3461+ On failure, sets the unit's workload state to 'blocked' and returns
3462+ False. Package installs remain queued.
3463+
3464+ On success, sets the apt.installed.{packagename} state for each
3465+ installed package and returns True.
3466+ '''
3467+ store = unitdata.kv()
3468+ queue = sorted((options, package)
3469+ for package, options in store.getrange('apt.install_queue.',
3470+ strip=True).items())
3471+
3472+ installed = set()
3473+ for options, batch in itertools.groupby(queue, lambda x: x[0]):
3474+ packages = [b[1] for b in batch]
3475+ try:
3476+ status_set(None, 'Installing {}'.format(','.join(packages)))
3477+ fetch.apt_install(packages, options, fatal=True)
3478+ store.unsetrange(packages, prefix='apt.install_queue.')
3479+ installed.update(packages)
3480+ except subprocess.CalledProcessError:
3481+ status_set('blocked',
3482+ 'Unable to install packages {}'
3483+ .format(','.join(packages)))
3484+ return False # Without setting reactive state.
3485+
3486+ for package in installed:
3487+ reactive.set_state('apt.installed.{}'.format(package))
3488+
3489+ reactive.remove_state('apt.queued_installs')
3490+ return True
3491+
3492+
3493+def ensure_package_status():
3494+ '''Hold or unhold packages per the package_status configuration option.
3495+
3496+ All packages installed using this module and handlers are affected.
3497+
3498+ An mechanism may be added in the future to override this for a
3499+ subset of installed packages.
3500+ '''
3501+ packages = installed()
3502+ if not packages:
3503+ return
3504+ config = hookenv.config()
3505+ package_status = config['package_status']
3506+ changed = reactive.helpers.data_changed('apt.package_status',
3507+ (package_status, sorted(packages)))
3508+ if changed:
3509+ if package_status == 'hold':
3510+ hookenv.log('Holding packages {}'.format(','.join(packages)))
3511+ fetch.apt_hold(packages)
3512+ else:
3513+ hookenv.log('Unholding packages {}'.format(','.join(packages)))
3514+ fetch.apt_unhold(packages)
3515+ reactive.remove_state('apt.needs_hold')
3516+
3517+
3518+def status_set(state, message):
3519+ """Set the unit's workload status.
3520+
3521+ Set state == None to keep the same state and just change the message.
3522+ """
3523+ if state is None:
3524+ state = hookenv.status_get()[0]
3525+ if state == 'unknown':
3526+ state = 'maintenance' # Guess
3527+ if state in ('error', 'blocked'):
3528+ lvl = hookenv.WARNING
3529+ else:
3530+ lvl = hookenv.INFO
3531+ hookenv.status_set(state, message)
3532+ hookenv.log('{}: {}'.format(state, message), lvl)
3533
3534=== added directory 'lib/charms/layer'
3535=== added file 'lib/charms/layer/__init__.py'
3536--- lib/charms/layer/__init__.py 1970-01-01 00:00:00 +0000
3537+++ lib/charms/layer/__init__.py 2017-02-15 08:51:47 +0000
3538@@ -0,0 +1,21 @@
3539+import os
3540+
3541+
3542+class LayerOptions(dict):
3543+ def __init__(self, layer_file, section=None):
3544+ import yaml # defer, might not be available until bootstrap
3545+ with open(layer_file) as f:
3546+ layer = yaml.safe_load(f.read())
3547+ opts = layer.get('options', {})
3548+ if section and section in opts:
3549+ super(LayerOptions, self).__init__(opts.get(section))
3550+ else:
3551+ super(LayerOptions, self).__init__(opts)
3552+
3553+
3554+def options(section=None, layer_file=None):
3555+ if not layer_file:
3556+ base_dir = os.environ.get('CHARM_DIR', os.getcwd())
3557+ layer_file = os.path.join(base_dir, 'layer.yaml')
3558+
3559+ return LayerOptions(layer_file, section)
3560
3561=== added file 'lib/charms/layer/basic.py'
3562--- lib/charms/layer/basic.py 1970-01-01 00:00:00 +0000
3563+++ lib/charms/layer/basic.py 2017-02-15 08:51:47 +0000
3564@@ -0,0 +1,159 @@
3565+import os
3566+import sys
3567+import shutil
3568+import platform
3569+from glob import glob
3570+from subprocess import check_call
3571+
3572+from charms.layer.execd import execd_preinstall
3573+
3574+
3575+def bootstrap_charm_deps():
3576+ """
3577+ Set up the base charm dependencies so that the reactive system can run.
3578+ """
3579+ # execd must happen first, before any attempt to install packages or
3580+ # access the network, because sites use this hook to do bespoke
3581+ # configuration and install secrets so the rest of this bootstrap
3582+ # and the charm itself can actually succeed. This call does nothing
3583+ # unless the operator has created and populated $CHARM_DIR/exec.d.
3584+ execd_preinstall()
3585+ # ensure that $CHARM_DIR/bin is on the path, for helper scripts
3586+ os.environ['PATH'] += ':%s' % os.path.join(os.environ['CHARM_DIR'], 'bin')
3587+ venv = os.path.abspath('../.venv')
3588+ vbin = os.path.join(venv, 'bin')
3589+ vpip = os.path.join(vbin, 'pip')
3590+ vpy = os.path.join(vbin, 'python')
3591+ if os.path.exists('wheelhouse/.bootstrapped'):
3592+ from charms import layer
3593+ cfg = layer.options('basic')
3594+ if cfg.get('use_venv') and '.venv' not in sys.executable:
3595+ # activate the venv
3596+ os.environ['PATH'] = ':'.join([vbin, os.environ['PATH']])
3597+ reload_interpreter(vpy)
3598+ return
3599+ # bootstrap wheelhouse
3600+ if os.path.exists('wheelhouse'):
3601+ with open('/root/.pydistutils.cfg', 'w') as fp:
3602+ # make sure that easy_install also only uses the wheelhouse
3603+ # (see https://github.com/pypa/pip/issues/410)
3604+ charm_dir = os.environ['CHARM_DIR']
3605+ fp.writelines([
3606+ "[easy_install]\n",
3607+ "allow_hosts = ''\n",
3608+ "find_links = file://{}/wheelhouse/\n".format(charm_dir),
3609+ ])
3610+ apt_install(['python3-pip', 'python3-setuptools', 'python3-yaml'])
3611+ from charms import layer
3612+ cfg = layer.options('basic')
3613+ # include packages defined in layer.yaml
3614+ apt_install(cfg.get('packages', []))
3615+ # if we're using a venv, set it up
3616+ if cfg.get('use_venv'):
3617+ if not os.path.exists(venv):
3618+ distname, version, series = platform.linux_distribution()
3619+ if series in ('precise', 'trusty'):
3620+ apt_install(['python-virtualenv'])
3621+ else:
3622+ apt_install(['virtualenv'])
3623+ cmd = ['virtualenv', '-ppython3', '--never-download', venv]
3624+ if cfg.get('include_system_packages'):
3625+ cmd.append('--system-site-packages')
3626+ check_call(cmd)
3627+ os.environ['PATH'] = ':'.join([vbin, os.environ['PATH']])
3628+ pip = vpip
3629+ else:
3630+ pip = 'pip3'
3631+ # save a copy of system pip to prevent `pip3 install -U pip`
3632+ # from changing it
3633+ if os.path.exists('/usr/bin/pip'):
3634+ shutil.copy2('/usr/bin/pip', '/usr/bin/pip.save')
3635+ # need newer pip, to fix spurious Double Requirement error:
3636+ # https://github.com/pypa/pip/issues/56
3637+ check_call([pip, 'install', '-U', '--no-index', '-f', 'wheelhouse',
3638+ 'pip'])
3639+ # install the rest of the wheelhouse deps
3640+ check_call([pip, 'install', '-U', '--no-index', '-f', 'wheelhouse'] +
3641+ glob('wheelhouse/*'))
3642+ if not cfg.get('use_venv'):
3643+ # restore system pip to prevent `pip3 install -U pip`
3644+ # from changing it
3645+ if os.path.exists('/usr/bin/pip.save'):
3646+ shutil.copy2('/usr/bin/pip.save', '/usr/bin/pip')
3647+ os.remove('/usr/bin/pip.save')
3648+ os.remove('/root/.pydistutils.cfg')
3649+ # flag us as having already bootstrapped so we don't do it again
3650+ open('wheelhouse/.bootstrapped', 'w').close()
3651+ # Ensure that the newly bootstrapped libs are available.
3652+ # Note: this only seems to be an issue with namespace packages.
3653+ # Non-namespace-package libs (e.g., charmhelpers) are available
3654+ # without having to reload the interpreter. :/
3655+ reload_interpreter(vpy if cfg.get('use_venv') else sys.argv[0])
3656+
3657+
3658+def reload_interpreter(python):
3659+ """
3660+ Reload the python interpreter to ensure that all deps are available.
3661+
3662+ Newly installed modules in namespace packages sometimes seemt to
3663+ not be picked up by Python 3.
3664+ """
3665+ os.execle(python, python, sys.argv[0], os.environ)
3666+
3667+
3668+def apt_install(packages):
3669+ """
3670+ Install apt packages.
3671+
3672+ This ensures a consistent set of options that are often missed but
3673+ should really be set.
3674+ """
3675+ if isinstance(packages, (str, bytes)):
3676+ packages = [packages]
3677+
3678+ env = os.environ.copy()
3679+
3680+ if 'DEBIAN_FRONTEND' not in env:
3681+ env['DEBIAN_FRONTEND'] = 'noninteractive'
3682+
3683+ cmd = ['apt-get',
3684+ '--option=Dpkg::Options::=--force-confold',
3685+ '--assume-yes',
3686+ 'install']
3687+ check_call(cmd + packages, env=env)
3688+
3689+
3690+def init_config_states():
3691+ import yaml
3692+ from charmhelpers.core import hookenv
3693+ from charms.reactive import set_state
3694+ from charms.reactive import toggle_state
3695+ config = hookenv.config()
3696+ config_defaults = {}
3697+ config_defs = {}
3698+ config_yaml = os.path.join(hookenv.charm_dir(), 'config.yaml')
3699+ if os.path.exists(config_yaml):
3700+ with open(config_yaml) as fp:
3701+ config_defs = yaml.safe_load(fp).get('options', {})
3702+ config_defaults = {key: value.get('default')
3703+ for key, value in config_defs.items()}
3704+ for opt in config_defs.keys():
3705+ if config.changed(opt):
3706+ set_state('config.changed')
3707+ set_state('config.changed.{}'.format(opt))
3708+ toggle_state('config.set.{}'.format(opt), config.get(opt))
3709+ toggle_state('config.default.{}'.format(opt),
3710+ config.get(opt) == config_defaults[opt])
3711+ hookenv.atexit(clear_config_states)
3712+
3713+
3714+def clear_config_states():
3715+ from charmhelpers.core import hookenv, unitdata
3716+ from charms.reactive import remove_state
3717+ config = hookenv.config()
3718+ remove_state('config.changed')
3719+ for opt in config.keys():
3720+ remove_state('config.changed.{}'.format(opt))
3721+ remove_state('config.set.{}'.format(opt))
3722+ remove_state('config.default.{}'.format(opt))
3723+ unitdata.kv().flush()
3724
3725=== added file 'lib/charms/layer/execd.py'
3726--- lib/charms/layer/execd.py 1970-01-01 00:00:00 +0000
3727+++ lib/charms/layer/execd.py 2017-02-15 08:51:47 +0000
3728@@ -0,0 +1,138 @@
3729+# Copyright 2014-2016 Canonical Limited.
3730+#
3731+# This file is part of layer-basic, the reactive base layer for Juju.
3732+#
3733+# charm-helpers is free software: you can redistribute it and/or modify
3734+# it under the terms of the GNU Lesser General Public License version 3 as
3735+# published by the Free Software Foundation.
3736+#
3737+# charm-helpers is distributed in the hope that it will be useful,
3738+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3739+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3740+# GNU Lesser General Public License for more details.
3741+#
3742+# You should have received a copy of the GNU Lesser General Public License
3743+# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
3744+
3745+# This module may only import from the Python standard library.
3746+import os
3747+import sys
3748+import subprocess
3749+import time
3750+
3751+'''
3752+execd/preinstall
3753+
3754+It is often necessary to configure and reconfigure machines
3755+after provisioning, but before attempting to run the charm.
3756+Common examples are specialized network configuration, enabling
3757+of custom hardware, non-standard disk partitioning and filesystems,
3758+adding secrets and keys required for using a secured network.
3759+
3760+The reactive framework's base layer invokes this mechanism as
3761+early as possible, before any network access is made or dependencies
3762+unpacked or non-standard modules imported (including the charms.reactive
3763+framework itself).
3764+
3765+Operators needing to use this functionality may branch a charm and
3766+create an exec.d directory in it. The exec.d directory in turn contains
3767+one or more subdirectories, each of which contains an executable called
3768+charm-pre-install and any other required resources. The charm-pre-install
3769+executables are run, and if successful, state saved so they will not be
3770+run again.
3771+
3772+ $CHARM_DIR/exec.d/mynamespace/charm-pre-install
3773+
3774+An alternative to branching a charm is to compose a new charm that contains
3775+the exec.d directory, using the original charm as a layer,
3776+
3777+A charm author could also abuse this mechanism to modify the charm
3778+environment in unusual ways, but for most purposes it is saner to use
3779+charmhelpers.core.hookenv.atstart().
3780+'''
3781+
3782+
3783+def default_execd_dir():
3784+ return os.path.join(os.environ['CHARM_DIR'], 'exec.d')
3785+
3786+
3787+def execd_module_paths(execd_dir=None):
3788+ """Generate a list of full paths to modules within execd_dir."""
3789+ if not execd_dir:
3790+ execd_dir = default_execd_dir()
3791+
3792+ if not os.path.exists(execd_dir):
3793+ return
3794+
3795+ for subpath in os.listdir(execd_dir):
3796+ module = os.path.join(execd_dir, subpath)
3797+ if os.path.isdir(module):
3798+ yield module
3799+
3800+
3801+def execd_submodule_paths(command, execd_dir=None):
3802+ """Generate a list of full paths to the specified command within exec_dir.
3803+ """
3804+ for module_path in execd_module_paths(execd_dir):
3805+ path = os.path.join(module_path, command)
3806+ if os.access(path, os.X_OK) and os.path.isfile(path):
3807+ yield path
3808+
3809+
3810+def execd_sentinel_path(submodule_path):
3811+ module_path = os.path.dirname(submodule_path)
3812+ execd_path = os.path.dirname(module_path)
3813+ module_name = os.path.basename(module_path)
3814+ submodule_name = os.path.basename(submodule_path)
3815+ return os.path.join(execd_path,
3816+ '.{}_{}.done'.format(module_name, submodule_name))
3817+
3818+
3819+def execd_run(command, execd_dir=None, stop_on_error=True, stderr=None):
3820+ """Run command for each module within execd_dir which defines it."""
3821+ if stderr is None:
3822+ stderr = sys.stdout
3823+ for submodule_path in execd_submodule_paths(command, execd_dir):
3824+ # Only run each execd once. We cannot simply run them in the
3825+ # install hook, as potentially storage hooks are run before that.
3826+ # We cannot rely on them being idempotent.
3827+ sentinel = execd_sentinel_path(submodule_path)
3828+ if os.path.exists(sentinel):
3829+ continue
3830+
3831+ try:
3832+ subprocess.check_call([submodule_path], stderr=stderr,
3833+ universal_newlines=True)
3834+ with open(sentinel, 'w') as f:
3835+ f.write('{} ran successfully {}\n'.format(submodule_path,
3836+ time.ctime()))
3837+ f.write('Removing this file will cause it to be run again\n')
3838+ except subprocess.CalledProcessError as e:
3839+ # Logs get the details. We can't use juju-log, as the
3840+ # output may be substantial and exceed command line
3841+ # length limits.
3842+ print("ERROR ({}) running {}".format(e.returncode, e.cmd),
3843+ file=stderr)
3844+ print("STDOUT<<EOM", file=stderr)
3845+ print(e.output, file=stderr)
3846+ print("EOM", file=stderr)
3847+
3848+ # Unit workload status gets a shorter fail message.
3849+ short_path = os.path.relpath(submodule_path)
3850+ block_msg = "Error ({}) running {}".format(e.returncode,
3851+ short_path)
3852+ try:
3853+ subprocess.check_call(['status-set', 'blocked', block_msg],
3854+ universal_newlines=True)
3855+ if stop_on_error:
3856+ sys.exit(0) # Leave unit in blocked state.
3857+ except Exception:
3858+ pass # We care about the exec.d/* failure, not status-set.
3859+
3860+ if stop_on_error:
3861+ sys.exit(e.returncode or 1) # Error state for pre-1.24 Juju
3862+
3863+
3864+def execd_preinstall(execd_dir=None):
3865+ """Run charm-pre-install for each module within execd_dir."""
3866+ execd_run('charm-pre-install', execd_dir=execd_dir)
3867
3868=== added file 'lib/charms/leadership.py'
3869--- lib/charms/leadership.py 1970-01-01 00:00:00 +0000
3870+++ lib/charms/leadership.py 2017-02-15 08:51:47 +0000
3871@@ -0,0 +1,58 @@
3872+# Copyright 2015-2016 Canonical Ltd.
3873+#
3874+# This file is part of the Leadership Layer for Juju.
3875+#
3876+# This program is free software: you can redistribute it and/or modify
3877+# it under the terms of the GNU General Public License version 3, as
3878+# published by the Free Software Foundation.
3879+#
3880+# This program is distributed in the hope that it will be useful, but
3881+# WITHOUT ANY WARRANTY; without even the implied warranties of
3882+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
3883+# PURPOSE. See the GNU General Public License for more details.
3884+#
3885+# You should have received a copy of the GNU General Public License
3886+# along with this program. If not, see <http://www.gnu.org/licenses/>.
3887+
3888+from charmhelpers.core import hookenv
3889+from charmhelpers.core import unitdata
3890+
3891+from charms import reactive
3892+from charms.reactive import not_unless
3893+
3894+
3895+__all__ = ['leader_get', 'leader_set']
3896+
3897+
3898+@not_unless('leadership.is_leader')
3899+def leader_set(settings=None, **kw):
3900+ '''Change leadership settings, per charmhelpers.core.hookenv.leader_set.
3901+
3902+ The leadership.set.{key} reactive state will be set while the
3903+ leadership hook environment setting remains set.
3904+
3905+ Changed leadership settings will set the leadership.changed.{key}
3906+ and leadership.changed states. These states will remain set until
3907+ the following hook.
3908+
3909+ These state changes take effect immediately on the leader, and
3910+ in future hooks run on non-leaders. In this way both leaders and
3911+ non-leaders can share handlers, waiting on these states.
3912+ '''
3913+ settings = settings or {}
3914+ settings.update(kw)
3915+ previous = unitdata.kv().getrange('leadership.settings.', strip=True)
3916+
3917+ for key, value in settings.items():
3918+ if value != previous.get(key):
3919+ reactive.set_state('leadership.changed.{}'.format(key))
3920+ reactive.set_state('leadership.changed')
3921+ reactive.helpers.toggle_state('leadership.set.{}'.format(key),
3922+ value is not None)
3923+ hookenv.leader_set(settings)
3924+ unitdata.kv().update(settings, prefix='leadership.settings.')
3925+
3926+
3927+def leader_get(attribute=None):
3928+ '''Return leadership settings, per charmhelpers.core.hookenv.leader_get.'''
3929+ return hookenv.leader_get(attribute)
3930
3931=== modified file 'metadata.yaml'
3932--- metadata.yaml 2015-12-09 06:50:47 +0000
3933+++ metadata.yaml 2017-02-15 08:51:47 +0000
3934@@ -1,14 +1,19 @@
3935-name: ibm-mq
3936-summary: IBM MQ Messaging product
3937-maintainer: Juju Support <jujusupp@us.ibm.com>
3938-description: |
3939- IBM MQ provides for messaging services to transport multiple types of
3940- data.
3941-tags:
3942- - misc
3943- - messaging
3944- - ibm
3945-subordinate: false
3946-provides:
3947- messaging:
3948- interface: ibm-mq
3949+"name": "ibm-mq"
3950+"summary": "IBM MQ messaging Product"
3951+"maintainer": "IBM Juju Support Team <jujusupp@us.ibm.com>"
3952+"description": |
3953+ IBM MQ provides for messaging services to transport multiple types of data.
3954+"tags":
3955+- "ibm"
3956+- "ibm"
3957+- "apt"
3958+- "leadership"
3959+- "mq"
3960+- "misc"
3961+- "ibm"
3962+- "messaging"
3963+"provides":
3964+ "messaging":
3965+ "interface": "ibm-mq"
3966+"min-juju-version": "2.0-beta1"
3967+"subordinate": !!bool "false"
3968
3969=== added directory 'reactive'
3970=== added file 'reactive/__init__.py'
3971=== added file 'reactive/apt.py'
3972--- reactive/apt.py 1970-01-01 00:00:00 +0000
3973+++ reactive/apt.py 2017-02-15 08:51:47 +0000
3974@@ -0,0 +1,131 @@
3975+# Copyright 2015-2016 Canonical Ltd.
3976+#
3977+# This file is part of the Apt layer for Juju.
3978+#
3979+# This program is free software: you can redistribute it and/or modify
3980+# it under the terms of the GNU General Public License version 3, as
3981+# published by the Free Software Foundation.
3982+#
3983+# This program is distributed in the hope that it will be useful, but
3984+# WITHOUT ANY WARRANTY; without even the implied warranties of
3985+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
3986+# PURPOSE. See the GNU General Public License for more details.
3987+#
3988+# You should have received a copy of the GNU General Public License
3989+# along with this program. If not, see <http://www.gnu.org/licenses/>.
3990+
3991+'''
3992+charms.reactive helpers for dealing with deb packages.
3993+
3994+Add apt package sources using add_source(). Queue deb packages for
3995+installation with install(). Configure and work with your software
3996+once the apt.installed.{packagename} state is set.
3997+'''
3998+import subprocess
3999+
4000+from charmhelpers import fetch
4001+from charmhelpers.core import hookenv
4002+from charmhelpers.core.hookenv import WARNING
4003+from charms import layer
4004+from charms import reactive
4005+from charms.reactive import when, when_not
4006+
4007+import charms.apt
4008+
4009+
4010+@when('apt.needs_update')
4011+def update():
4012+ charms.apt.update()
4013+
4014+
4015+@when('apt.queued_installs')
4016+@when_not('apt.needs_update')
4017+def install_queued():
4018+ charms.apt.install_queued()
4019+
4020+
4021+@when_not('apt.queued_installs')
4022+def ensure_package_status():
4023+ charms.apt.ensure_package_status()
4024+
4025+
4026+def filter_installed_packages(packages):
4027+ # Don't use fetch.filter_installed_packages, as it depends on python-apt
4028+ # and not available if the basic layer's use_site_packages option is off
4029+ # TODO: Move this to charm-helpers.fetch
4030+ cmd = ['dpkg-query', '--show', r'--showformat=${Package}\n']
4031+ installed = set(subprocess.check_output(cmd,
4032+ universal_newlines=True).split())
4033+ return set(packages) - installed
4034+
4035+
4036+def clear_removed_package_states():
4037+ """On hook startup, clear install states for removed packages."""
4038+ removed = filter_installed_packages(charms.apt.installed())
4039+ if removed:
4040+ hookenv.log('{} missing packages ({})'.format(len(removed),
4041+ ','.join(removed)),
4042+ WARNING)
4043+ for package in removed:
4044+ reactive.remove_state('apt.installed.{}'.format(package))
4045+
4046+
4047+def configure_sources():
4048+ """Add user specified package sources from the service configuration.
4049+
4050+ See charmhelpers.fetch.configure_sources for details.
4051+ """
4052+ config = hookenv.config()
4053+
4054+ # We don't have enums, so we need to validate this ourselves.
4055+ package_status = config.get('package_status')
4056+ if package_status not in ('hold', 'install'):
4057+ charms.apt.status_set('blocked',
4058+ 'Unknown package_status {}'
4059+ ''.format(package_status))
4060+ # Die before further hooks are run. This isn't very nice, but
4061+ # there is no other way to inform the operator that they have
4062+ # invalid configuration.
4063+ raise SystemExit(0)
4064+
4065+ sources = config.get('install_sources')
4066+ keys = config.get('install_keys')
4067+ if reactive.helpers.data_changed('apt.configure_sources', (sources, keys)):
4068+ fetch.configure_sources(update=False,
4069+ sources_var='install_sources',
4070+ keys_var='install_keys')
4071+ reactive.set_state('apt.needs_update')
4072+
4073+ extra_packages = sorted(config.get('extra_packages', '').split())
4074+ if extra_packages:
4075+ charms.apt.queue_install(extra_packages)
4076+
4077+
4078+def queue_layer_packages():
4079+ """Add packages listed in build-time layer options."""
4080+ # Both basic and apt layer. basic layer will have already installed
4081+ # its defined packages, but rescheduling it here gets the apt layer
4082+ # state set and they will pinned as any other apt layer installed
4083+ # package.
4084+ opts = layer.options()
4085+ for section in ['basic', 'apt']:
4086+ if section in opts and 'packages' in opts[section]:
4087+ charms.apt.queue_install(opts[section]['packages'])
4088+
4089+
4090+# Per https://github.com/juju-solutions/charms.reactive/issues/33,
4091+# this module may be imported multiple times so ensure the
4092+# initialization hook is only registered once. I have to piggy back
4093+# onto the namespace of a module imported before reactive discovery
4094+# to do this.
4095+if not hasattr(reactive, '_apt_registered'):
4096+ # We need to register this to run every hook, not just during install
4097+ # and config-changed, to protect against race conditions. If we don't
4098+ # do this, then the config in the hook environment may show updates
4099+ # to running hooks well before the config-changed hook has been invoked
4100+ # and the intialization provided an opertunity to be run.
4101+ hookenv.atstart(hookenv.log, 'Initializing Apt Layer')
4102+ hookenv.atstart(clear_removed_package_states)
4103+ hookenv.atstart(configure_sources)
4104+ hookenv.atstart(queue_layer_packages)
4105+ reactive._apt_registered = True
4106
4107=== added file 'reactive/ibm-base.sh'
4108--- reactive/ibm-base.sh 1970-01-01 00:00:00 +0000
4109+++ reactive/ibm-base.sh 2017-02-15 08:51:47 +0000
4110@@ -0,0 +1,107 @@
4111+#!/bin/bash
4112+source charms.reactive.sh
4113+set -e
4114+
4115+
4116+# Utility function to verify a downloaded resource
4117+# :param: file name
4118+# :param: checksum type
4119+# :param: checksum value
4120+function verify_curl_resource() {
4121+ local FILE=$1
4122+ local TYPE=$2
4123+ local EXPECTED_SUM=$3
4124+ local CALCULATED_SUM=""
4125+ local PROG=""
4126+
4127+ if [ ! -r ${FILE} ]; then
4128+ status-set blocked "ibm-base: could not read ${FILE}"
4129+ juju-log "Could not verify the downloaded resource. File could not be read: ${FILE}"
4130+ fi
4131+
4132+ # Set our checksum utility based on the requested type
4133+ case "${TYPE}" in
4134+ md5)
4135+ PROG='md5sum'
4136+ ;;
4137+ sha256)
4138+ PROG='sha256sum'
4139+ ;;
4140+ sha512)
4141+ PROG='sha512sum'
4142+ ;;
4143+ *)
4144+ status-set blocked "ibm-base: checksum type must be md5, sha215, or sha512"
4145+ juju-log "Could not verify the downloaded resource ${FILE}. Unknown checksum type: ${TYPE}"
4146+ return 1
4147+ esac
4148+
4149+ CALCULATED_SUM=`${PROG} ${FILE} | awk '{print $1}'`
4150+ if [ "${CALCULATED_SUM}" = "${EXPECTED_SUM}" ]; then
4151+ juju-log "Checksum verified for ${FILE}."
4152+ return 0
4153+ else
4154+ status-set blocked "ibm-base: checksums did not match"
4155+ juju-log "Checksum mismatch for ${FILE}. Expected ${EXPECTED_SUM}, got ${CALCULATED_SUM}"
4156+ return 1
4157+ fi
4158+}
4159+
4160+
4161+# Fetch curl resources if a URL is configured
4162+@when 'config.set.curl_url'
4163+@when_any 'config.new.curl_url' 'config.changed.curl_url' 'config.new.curl_opts' 'config.changed.curl_opts'
4164+function fetch_curl_resource() {
4165+ local ARCHIVE_DIR="${CHARM_DIR}/files/archives"
4166+ local CURL_URL=$(config-get 'curl_url')
4167+ local CURL_OPTS=$(config-get 'curl_opts')
4168+
4169+ status-set maintenance "ibm-base: fetching resource(s)"
4170+
4171+ mkdir -p ${ARCHIVE_DIR}
4172+ cd ${ARCHIVE_DIR}
4173+ # Multiple URLs may be separated by a space, so loop.
4174+ for URL_STRING in ${CURL_URL}
4175+ do
4176+ # For each URL_STRING, set the url, checksum type, and checksum value.
4177+ local URL=${URL_STRING%%\?*} # string before the first '?'
4178+ local FILE_NAME=${URL##*\/} # string after the last '/'
4179+ local SUM_STRING=${URL_STRING#*\?} # string after the first '?'
4180+ local SUM_TYPE=${SUM_STRING%%\=*} # string before the first '='
4181+ local SUM_VALUE=${SUM_STRING#*\=} # string after the first '='
4182+
4183+ if [ -z ${FILE_NAME} ]; then
4184+ FILE_NAME="juju-${RANDOM}"
4185+ fi
4186+ curl --silent --show-error ${CURL_OPTS} -o ${FILE_NAME} ${URL}
4187+
4188+ # Verify our resource checksum. If this fails, let verify_resource log
4189+ # the reason and exit successfully. Exiting non-zero would fail the hook,
4190+ # so return 0 and simply inform the user that verification failed.
4191+ verify_curl_resource ${FILE_NAME} ${SUM_TYPE} ${SUM_VALUE} || return 0
4192+ done
4193+ cd -
4194+
4195+ set_state 'ibm-base.curl.resource.fetched'
4196+ status-set active "ibm-base: curl resource(s) fetched"
4197+ juju-log 'Curl resource fetched'
4198+}
4199+
4200+
4201+# Handle license acceptance
4202+@when 'config.changed.license_accepted'
4203+function check_license_acceptance() {
4204+ local LIC_ACCEPTED=$(config-get 'license_accepted')
4205+
4206+ # compare lowercase LIC_ACCEPTED (requires bash > 4)
4207+ if [ "${LIC_ACCEPTED,,}" = "true" ]; then
4208+ set_state 'ibm-base.license.accepted'
4209+ juju-log 'License accepted'
4210+ else
4211+ remove_state 'ibm-base.license.accepted'
4212+ juju-log 'License NOT accepted'
4213+ fi
4214+}
4215+
4216+# Main reactive entry point
4217+reactive_handler_main
4218
4219=== added file 'reactive/ibm-mq.sh'
4220--- reactive/ibm-mq.sh 1970-01-01 00:00:00 +0000
4221+++ reactive/ibm-mq.sh 2017-02-15 08:51:47 +0000
4222@@ -0,0 +1,340 @@
4223+#!/bin/bash
4224+
4225+set -ex
4226+source charms.reactive.sh
4227+
4228+ARCHIVE_DIR=$CHARM_DIR/files/archives
4229+MQ_INSTALL_PATH=/opt/mqm
4230+ARCHITECTURE=`uname -m`
4231+RPM_INSTALL_ARG="-ivh --nodeps --force-debian"
4232+relatedService=""
4233+QMA=""
4234+QM_Name=""
4235+hostname=`unit-get private-address`
4236+ if [ "$ARCHITECTURE" = "ppc64le" ]; then
4237+ RPM_INSTALL_ARG="-ivh --nodeps --force-debian --ignorearch"
4238+ fi
4239+
4240+
4241+# Check whether IBM MQ is installed
4242+
4243+is_mq_installed()
4244+{
4245+ if [ -d $MQ_INSTALL_PATH/bin ]; then
4246+ source $MQ_INSTALL_PATH/bin/setmqenv -s
4247+ if [ $? == 0 ]; then
4248+ source $MQ_INSTALL_PATH/bin/setmqenv -s
4249+ echo "True"
4250+ fi
4251+ else
4252+ echo "False"
4253+ fi
4254+}
4255+
4256+# Remove MQ, if installed
4257+remove_software()
4258+{
4259+
4260+ mq_installed=`is_mq_installed`
4261+ if [ $mq_installed == True ]; then
4262+ juju-log "Removing IBM MQ software."
4263+ # Stop all queues
4264+ questr="QMNAME("
4265+ for queue in `$MQ_INSTALL_PATH/bin/dspmq -o installation | cut -d' ' -f1 `;
4266+ do
4267+ # Get the queue manager name
4268+ queue_mgr_name=${queue:${#questr}:${#queue}-${#questr}-1}
4269+ juju-log "Stopping queue manager $queue_mgr_name"
4270+ set +e
4271+ su -l mqm -c "$MQ_INSTALL_PATH/bin/endmqm -w $queue_mgr_name"
4272+ su -l mqm -c "$MQ_INSTALL_PATH/bin/endmqlsr -w -m $queue_mgr_name"
4273+ set -e
4274+ done
4275+ # Get list of packages and remove them
4276+ mq_rpms="`rpm -qa | grep MQSeries`"
4277+ juju-log "Removing package(s) $mq_rpms"
4278+ rpm -ev --force-debian $mq_rpms
4279+ fi
4280+
4281+}
4282+
4283+# Update system configuration after installing MQ
4284+configure_system()
4285+{
4286+ juju-log "IBM MQ: Updating system configuration."
4287+ # Some containers do not allow system updates.
4288+ # Prevent the script from failing in such cases
4289+ set +e
4290+
4291+ # Add user ubuntu to mqm group
4292+ adduser ubuntu mqm
4293+
4294+ # Update mqm file limits
4295+
4296+ hard_nf=`su - mqm -c "ulimit -Hn"`
4297+ soft_nf=`su - mqm -c "ulimit -Sn"`
4298+
4299+ if [ $hard_nf -lt 10240 -o $soft_nf -lt 10240 ]; then
4300+ if [ "`grep mqm /etc/security/limits.conf`"=="" ]; then
4301+ sed -i 's/# End of file/mqm hard nofile 10240\nmqm soft nofile 10240\n# End of file/' /etc/security/limits.conf
4302+ fi
4303+ #Update /etc/pam.d/common-session file to take effect the above changes
4304+ result_search=`grep 'session required pam_limits.so' /etc/pam.d/common-session`
4305+ if [ -z "$result_search" ]; then
4306+ sed -i 's/# end of pam-auth-update config/session required pam_limits.so\n# end of pam-auth-update config/' /etc/pam.d/common-session
4307+ fi
4308+ fi
4309+
4310+ set -e
4311+ juju-log "IBM MQ: Completed system configuration update."
4312+
4313+}
4314+
4315+# create Queue manager and Queue
4316+create_QM_Queue()
4317+{
4318+
4319+ juju-log "IBM MQ: Verifying installation."
4320+
4321+ relatedService=$1
4322+ consumer_hostname=$2
4323+
4324+ QMA="${relatedService//-}.queue.manager"
4325+
4326+ # Prevent the script from failing on failure.
4327+ # It could because configure_system call failed
4328+ set +e
4329+
4330+ # Run as mqm user as root will not work
4331+
4332+ # Create queue manager
4333+ juju-log "IBM MQ: Create queue manager $QMA."
4334+ su -l mqm -c "$MQ_INSTALL_PATH/bin/crtmqm $QMA"
4335+ rc=$?
4336+ if [ $rc == 0 ]; then
4337+ juju-log "IBM MQ: queue manager $QMA created."
4338+ QM_create=1
4339+ elif [ $rc == 8 ]; then
4340+ QM_exists=1
4341+ else
4342+ juju-log "IBM MQ: Failed to create queue manager $QMA."
4343+ fi
4344+
4345+ # Start queue manager
4346+ if [ $QM_create -eq 1 ]; then
4347+ juju-log "IBM MQ: Starting queue manager $QMA."
4348+ su -l mqm -c "$MQ_INSTALL_PATH/bin/strmqm $QMA"
4349+ if [ $? == 0 ]; then
4350+ juju-log "IBM MQ: Queue manager $QMA started."
4351+ else
4352+ juju-log "IBM MQ: Failed to start queue manager $QMA."
4353+
4354+ fi
4355+
4356+ # set the port to listen queue manager
4357+ new_port=1414
4358+ free_port=0
4359+ is_free=0
4360+
4361+ #Check whether the port number is already in use
4362+ while [ $free_port -eq 0 ]
4363+ do
4364+
4365+ #is_free=`netstat -lnp | grep $new_port | cut -d":" -f2 | cut -d" " -f1`
4366+ is_free=`netstat -an | grep $new_port | grep LISTEN`
4367+ if [ -z "$is_free" ]; then
4368+ free_port=1
4369+ else
4370+ new_port=$((new_port+1))
4371+ fi
4372+
4373+ done
4374+
4375+ juju-log "IBM MQ: Setting port to $new_port"
4376+
4377+ #start queue manager in listener mode
4378+ juju-log "IBM MQ: Starting queue manager $QMA in listener mode"
4379+ su -l mqm -c "$MQ_INSTALL_PATH/bin/runmqlsr -m $QMA -t tcp -p $new_port" &
4380+ if [ $? == 0 ]; then
4381+ juju-log "IBM MQ: Queue manager $QMA listening on $new_port"
4382+ else
4383+ juju-log "IBM MQ: Queue manager $QMA failed to listen"
4384+ fi
4385+
4386+ # Create queue and setup the queue configuration for consumer service
4387+ juju-log "IBM MQ: Creating queue."
4388+
4389+ sudo bash -c "cat > $CHARM_DIR/files/archives/${relatedService//-}_mq_create_queue.mqsc << EOF
4390+DEFINE QLOCAL (QUEUE1)
4391+DEFINE CHANNEL ('MDB.SVRCONN') CHLTYPE(SVRCONN) TRPTYPE(TCP)
4392+SET CHLAUTH ('MDB.SVRCONN') TYPE(ADDRESSMAP) ADDRESS($consumer_hostname) MCAUSER('mqm')
4393+SET AUTHREC OBJTYPE(QMGR) PRINCIPAL('mqm') AUTHADD(CONNECT,INQ)
4394+SET AUTHREC PROFILE('QUEUE1') OBJTYPE(QUEUE) PRINCIPAL('mqm') AUTHADD(PUT,GET,INQ,BROWSE)
4395+SET AUTHREC PROFILE('SYSTEM.BASE.TOPIC') OBJTYPE(TOPIC) PRINCIPAL('mqm') AUTHADD(PUB, SUB)
4396+EOF"
4397+ if [ $? == 0 ]; then
4398+ # Create file to delete the above queue when relation breaks
4399+ sudo bash -c "echo 'DELETE QLOCAL(QUEUE1) PURGE' > $CHARM_DIR/files/archives/${relatedService//-}_mq_delete_queue.mqsc"
4400+ fi
4401+
4402+ if [ -f $CHARM_DIR/files/archives/${relatedService//-}_mq_create_queue.mqsc ]; then
4403+
4404+ su -l mqm -c "$MQ_INSTALL_PATH/bin/runmqsc $QMA < $CHARM_DIR/files/archives/${relatedService//-}_mq_create_queue.mqsc"
4405+ if [ $? == 0 ]; then
4406+ juju-log "IBM MQ: Queue created."
4407+ else
4408+ juju-log "IBM MQ: Failed to create queue."
4409+ fi
4410+
4411+ QM_Name=$QMA
4412+ Qname=$CHARM_DIR/files/archives/${relatedService//-}_mq_create_queue.mqsc
4413+ port=$new_port
4414+ open-port $port/TCP
4415+
4416+ juju-log "create_QM_Queue values are QM_name=$QM_Name Qname=$Qname hostname=$hostname port=$port"
4417+ fi
4418+ elif [ $QM_exists -eq 1 ];then
4419+ QM_Name=$QMA
4420+ Qname=$CHARM_DIR/files/archives/${relatedService//-}_mq_create_queue.mqsc
4421+ port=`ps -ef| grep runmqlsr | grep "$QMA" | grep -v grep | grep "su -l mqm" | awk -F"-p " '{print $2}'`
4422+ juju-log "relation with ${relatedService} already exists"
4423+ juju-log "create_QM_Queue values are QM_name=$QM_Name Qname=$Qname hostname=$hostname port=$port"
4424+ fi
4425+
4426+}
4427+
4428+
4429+# IBM MQ: Deleting queue
4430+delete_QM_Queue()
4431+{
4432+
4433+ relatedService=$1
4434+
4435+ QMA="${relatedService//-}.queue.manager"
4436+ # Delete queue
4437+ juju-log "IBM MQ: Deleting queue."
4438+ su -l mqm -c "$MQ_INSTALL_PATH/bin/runmqsc $QMA < $CHARM_DIR/files/archives/${relatedService//-}_mq_delete_queue.mqsc"
4439+ if [ $? == 0 ]; then
4440+ juju-log "IBM MQ: Deleted queue."
4441+ else
4442+ juju-log "IBM MQ: Failed to delete queue."
4443+ fi
4444+
4445+
4446+ port=`ps -ef| grep runmqlsr | grep "$QMA" | grep -v grep | grep "su -l mqm" | awk -F"-p " '{print $2}'`
4447+
4448+ juju-log "IBM MQ: stop Queue manager from listening on port"
4449+ su -l mqm -c "$MQ_INSTALL_PATH/bin/endmqlsr -m $QMA -w"
4450+ if [ $? == 0 ]; then
4451+ juju-log "IBM MQ: Stopped queue manager from listening."
4452+ else
4453+ juju-log "IBM MQ: Failed to stop queue manager to listen."
4454+ fi
4455+
4456+ #close-port the open-port for the consumer
4457+ close-port $port
4458+
4459+ # End queue manger
4460+ juju-log "IBM MQ: Stopping queue manager."
4461+ su -l mqm -c "$MQ_INSTALL_PATH/bin/endmqm $QMA"
4462+ su -l mqm -c "$MQ_INSTALL_PATH/bin/endmqlsr -w -m $QMA"
4463+ if [ $? -eq 0 ]; then
4464+ juju-log "IBM MQ: Queue Manager is stopped."
4465+ else
4466+ juju-log "IBM MQ: Queue Manager failed to stop."
4467+ fi
4468+
4469+ sleep 10
4470+
4471+ juju-log "IBM MQ: Deleting queue manager."
4472+ su -l mqm -c "$MQ_INSTALL_PATH/bin/dltmqm $QMA"
4473+ if [ $? -eq 0 ]; then
4474+ juju-log "IBM MQ: Queue Manager is deleted."
4475+ else
4476+ juju-log "IBM MQ: Queue Manager could not be deleted."
4477+ fi
4478+
4479+ #clean up the created MQSC files
4480+ if [ -f $CHARM_DIR/files/archives/${relatedService//-}_mq_create_queue.mqsc ];then
4481+ sudo bash -c "rm $CHARM_DIR/files/archives/${relatedService//-}_mq_create_queue.mqsc"
4482+ fi
4483+ if [ -f $CHARM_DIR/files/archives/${relatedService//-}_mq_delete_queue.mqsc ]; then
4484+ sudo bash -c "rm $CHARM_DIR/files/archives/${relatedService//-}_mq_delete_queue.mqsc"
4485+ fi
4486+
4487+ set -e
4488+ juju-log "IBM MQ: Deleting queue completed."
4489+}
4490+
4491+@when 'ibm-base.license.accepted' 'ibm-base.curl.resource.fetched'
4492+@when_not 'ibm-mq.installed'
4493+function mq_install() {
4494+ juju-log "inside mq install function."
4495+ mq_inst=`is_mq_installed`
4496+ juju-log "mq_inst==$mq_inst"
4497+ if [ -f $CHARM_DIR/files/archives/*.gz ]; then
4498+ juju-log "IBM MQ: Extracting IBM MQ package."
4499+ tar xvfz $CHARM_DIR/files/archives/*.gz --strip-components=1 -C $CHARM_DIR/files/archives
4500+ rm -rf $CHARM_DIR/files/archives/*.gz
4501+ juju-log "IBM MQ: Extracted IBM MQ package."
4502+ juju-log "Awaiting acceptance of IBM MQ license (see README on how to accept the license)."
4503+ fi
4504+ if [ $mq_inst == False ]; then
4505+ # Check MQ package availability
4506+ if [ -f $CHARM_DIR/files/archives/MQSeriesServer*rpm ] && [ -f $CHARM_DIR/files/archives/MQSeriesRuntime*rpm ];
4507+ then
4508+ echo "MQ Packages available for installation.";
4509+ $CHARM_DIR/files/archives/mqlicense.sh -accept
4510+ juju-log "Installing available MQ packages."
4511+ rpm $RPM_INSTALL_ARG --prefix $MQ_INSTALL_PATH $CHARM_DIR/files/archives/MQSeries*rpm
4512+ juju-log "Installation of available MQ packages complete."
4513+ # Configure system values for MQ
4514+ configure_system
4515+ set_state 'ibm-mq.installed'
4516+ status-set active "MQ installed successfully"
4517+ else
4518+ status-set blocked "MQ Packages missing. Please check README file Upgrade MQ charm after adding the MQ packages"
4519+ fi
4520+ fi
4521+}
4522+
4523+@when 'ibm-mq.installed'
4524+@when 'messaging.connected'
4525+function export_mq_details(){
4526+ juju-log "on ibm_mq.connected triggered here"
4527+ services=$(relation_call --state=messaging.connected services) || true
4528+ for service in $services; do
4529+ juju-log "$service"
4530+ consumer_hostname=$(relation_call --state=messaging.connected get_consumer_hostname) || true
4531+ create_QM_Queue $service $consumer_hostname
4532+ juju-log "create_QM_Queue values are QM_name=$QM_Name Qname=$Qname hostname=$hostname port=$port"
4533+ relation_call --state=messaging.connected set_mq_details $service $QM_Name $Qname $hostname $port || true
4534+ done
4535+}
4536+
4537+@when 'messaging.departed' 'ibm-mq.installed'
4538+function remove_mq_QM_Q() {
4539+ juju-log "in delete_QM_Queue.."
4540+ services=$(relation_call --state=messaging.departed services) || true
4541+ for service in $services; do
4542+ juju-log "$service"
4543+ delete_QM_Queue $service
4544+ juju-log "delete_QM_Queue done."
4545+ juju-log "Calling dismiss for $service"
4546+ relation_call --state=messaging.departed dismiss $service || true
4547+ done
4548+ }
4549+
4550+@when 'ibm-mq.installed'
4551+@when_not 'ibm-base.license.accepted'
4552+function uninstall() {
4553+ # Uninstall all versions of IBM MQ
4554+ status-set maintenance "Uninstalling IBM MQ"
4555+ mq_license_accepted=`config-get accept-ibm-mq-license`
4556+ remove_software
4557+ remove_state 'ibm-mq.installed'
4558+ remove_state 'messaging.connected'
4559+ status-set blocked "IBM MQ Uninstalled"
4560+}
4561+
4562+reactive_handler_main
4563
4564=== added file 'reactive/leadership.py'
4565--- reactive/leadership.py 1970-01-01 00:00:00 +0000
4566+++ reactive/leadership.py 2017-02-15 08:51:47 +0000
4567@@ -0,0 +1,68 @@
4568+# Copyright 2015-2016 Canonical Ltd.
4569+#
4570+# This file is part of the Leadership Layer for Juju.
4571+#
4572+# This program is free software: you can redistribute it and/or modify
4573+# it under the terms of the GNU General Public License version 3, as
4574+# published by the Free Software Foundation.
4575+#
4576+# This program is distributed in the hope that it will be useful, but
4577+# WITHOUT ANY WARRANTY; without even the implied warranties of
4578+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
4579+# PURPOSE. See the GNU General Public License for more details.
4580+#
4581+# You should have received a copy of the GNU General Public License
4582+# along with this program. If not, see <http://www.gnu.org/licenses/>.
4583+
4584+from charmhelpers.core import hookenv
4585+from charmhelpers.core import unitdata
4586+
4587+from charms import reactive
4588+from charms.leadership import leader_get, leader_set
4589+
4590+
4591+__all__ = ['leader_get', 'leader_set'] # Backwards compatibility
4592+
4593+
4594+def initialize_leadership_state():
4595+ '''Initialize leadership.* states from the hook environment.
4596+
4597+ Invoked by hookenv.atstart() so states are available in
4598+ @hook decorated handlers.
4599+ '''
4600+ is_leader = hookenv.is_leader()
4601+ if is_leader:
4602+ hookenv.log('Initializing Leadership Layer (is leader)')
4603+ else:
4604+ hookenv.log('Initializing Leadership Layer (is follower)')
4605+
4606+ reactive.helpers.toggle_state('leadership.is_leader', is_leader)
4607+
4608+ previous = unitdata.kv().getrange('leadership.settings.', strip=True)
4609+ current = hookenv.leader_get()
4610+
4611+ # Handle deletions.
4612+ for key in set(previous.keys()) - set(current.keys()):
4613+ current[key] = None
4614+
4615+ any_changed = False
4616+ for key, value in current.items():
4617+ reactive.helpers.toggle_state('leadership.changed.{}'.format(key),
4618+ value != previous.get(key))
4619+ if value != previous.get(key):
4620+ any_changed = True
4621+ reactive.helpers.toggle_state('leadership.set.{}'.format(key),
4622+ value is not None)
4623+ reactive.helpers.toggle_state('leadership.changed', any_changed)
4624+
4625+ unitdata.kv().update(current, prefix='leadership.settings.')
4626+
4627+
4628+# Per https://github.com/juju-solutions/charms.reactive/issues/33,
4629+# this module may be imported multiple times so ensure the
4630+# initialization hook is only registered once. I have to piggy back
4631+# onto the namespace of a module imported before reactive discovery
4632+# to do this.
4633+if not hasattr(reactive, '_leadership_registered'):
4634+ hookenv.atstart(initialize_leadership_state)
4635+ reactive._leadership_registered = True
4636
4637=== added file 'reactive/test'
4638--- reactive/test 1970-01-01 00:00:00 +0000
4639+++ reactive/test 2017-02-15 08:51:47 +0000
4640@@ -0,0 +1,1 @@
4641+DEFINE QLOCAL (QUEUE1)
4642
4643=== added file 'requirements.txt'
4644--- requirements.txt 1970-01-01 00:00:00 +0000
4645+++ requirements.txt 2017-02-15 08:51:47 +0000
4646@@ -0,0 +1,2 @@
4647+flake8
4648+pytest
4649
4650=== modified file 'revision'
4651--- revision 2015-03-11 19:52:30 +0000
4652+++ revision 2017-02-15 08:51:47 +0000
4653@@ -1,1 +1,1 @@
4654-1
4655+1
4656\ No newline at end of file
4657
4658=== modified file 'tests/00-setup'
4659--- tests/00-setup 2015-06-10 10:41:46 +0000
4660+++ tests/00-setup 2017-02-15 08:51:47 +0000
4661@@ -1,5 +1,17 @@
4662 #!/bin/bash
4663+MQ_CURL_URL=${MQ_CURL_URL?Error: IBM MQ curl_url be defined in tests/00-setup}
4664+MQ_CURL_OPTS=${MQ_CURL_OPTS?Error: IBM MQ curl_OPTS be defined in tests/00-setup}
4665+MQ_LICENSE=${MQ_LICENSE?Error: IBM MQ License accepted value must be defined in tests/00-setup}
4666+
4667+# Add a local configuration file
4668+cat << EOF > local.yaml
4669+mq:
4670+ mq_curl_url: "$MQ_CURL_URL"
4671+ mq_curl_opts: "$MQ_CURL_OPTS"
4672+ mq_license_accepted: "$MQ_LICENSE"
4673+EOF
4674
4675 sudo add-apt-repository ppa:juju/stable -y
4676 sudo apt-get update
4677-sudo apt-get install amulet -y
4678+sudo apt-get -y install unzip
4679+sudo apt-get install amulet python3 -y
4680
4681=== modified file 'tests/10-bundles-test.py'
4682--- tests/10-bundles-test.py 2015-07-15 08:13:51 +0000
4683+++ tests/10-bundles-test.py 2017-02-15 08:51:47 +0000
4684@@ -1,11 +1,11 @@
4685 #!/usr/bin/env python3
4686-
4687 # This amulet test deploys the bundles.yaml file in this directory.
4688
4689 import os
4690 import unittest
4691 import yaml
4692 import amulet
4693+import sys
4694
4695 # Lots of prereqs on this charm (eg: java), so give it a large timeout
4696 seconds_to_wait = 1500
4697@@ -16,14 +16,41 @@
4698 @classmethod
4699 def setUpClass(cls):
4700 """ Set up an amulet deployment using the bundle. """
4701- d = amulet.Deployment()
4702- bundle_path = os.path.join(os.path.dirname(__file__), 'bundles.yaml')
4703- with open(bundle_path, 'r') as bundle_file:
4704- contents = yaml.safe_load(bundle_file)
4705- d.load(contents)
4706+ d = amulet.Deployment(juju_env='local', series='trusty')
4707+ local_path = os.path.join(os.path.dirname(__file__), 'local.yaml')
4708+ with open(local_path, "r") as fd:
4709+ config = yaml.safe_load(fd)
4710+ curl_url = config.get('mq').get('mq_curl_url')
4711+ print('Using mq_curl_url %s' % curl_url)
4712+ # Test if a IBM mq curl url is defined
4713+ if not curl_url:
4714+ print("You need to define a im_curl_url.\n"
4715+ "Edit local.yaml or tests/00-setup and run it again.")
4716+ sys.exit(1)
4717+
4718+ curl_opts = config.get('mq').get('mq_curl_opts')
4719+ print('Using mq_curl_opts %s' % curl_opts)
4720+ # Test if IBM curl_opts is defined
4721+ if not curl_opts:
4722+ print("You need to define a curl_opts."
4723+ " repository.\n Edit local.yaml or tests/00-setup"
4724+ " and run it again.")
4725+ sys.exit(1)
4726+ license_accepted = config.get('mq').get('mq_license_accepted')
4727+ print('Using license_accepted %s' % license_accepted)
4728+ # Test if a license_accepted is defined
4729+ if not license_accepted:
4730+ print("You need to define a license_accepted for the IBM MQ"
4731+ " product repository.\n Edit local.yaml or tests/00-setup"
4732+ " and run it again.")
4733+ sys.exit(1)
4734+
4735+ d.add('ibm-mq')
4736
4737 # Software doesn't actually install until you accept the license
4738- d.configure('ibm-mq', {'accept-ibm-mq-license': True})
4739+ d.configure('ibm-mq', {'license_accepted': license_accepted,
4740+ 'curl_url': curl_url,
4741+ 'curl_opts': curl_opts})
4742
4743 d.setup(seconds_to_wait)
4744 d.sentry.wait(seconds_to_wait)
4745@@ -34,22 +61,26 @@
4746 self.assertTrue(self.d.deployed)
4747
4748 def test_running(self):
4749- """ Test that, if deployed, everything is set up and running correctly. """
4750- ibm_mq_unit = self.d.sentry.unit['ibm-mq/0']
4751+ """ Test that, if deployed,
4752+ everything is set up and running correctly. """
4753+ ibm_mq_unit = self.d.sentry.unit['ibm-mq'][0]
4754
4755 # Configure system values
4756 output, code = ibm_mq_unit.run('sysctl kernel.shmmax | cut -d= -f2')
4757 print(output)
4758 if code != 0 or int(output) < 268435456:
4759- # this isn't really a pass/fail test, as it depends on the environment
4760- # to which the charm is deployed, and doesn't block the install
4761- print('Unable to set kernel.shmmax value (perhaps on local provider?).')
4762+ # this isn't really a pass/fail test, as it depends
4763+ # on the environment to which
4764+ # the charm is deployed, and doesn't block the install
4765+ print('Unable to set kernel.shmmax'
4766+ ' value (perhaps on local provider?).')
4767
4768 output, code = ibm_mq_unit.run('sysctl fs.file-max | cut -d= -f2')
4769 print(output)
4770 if code != 0 or int(output) < 524288:
4771- # this isn't really a pass/fail test, as it depends on the environment
4772- # to which the charm is deployed, and doesn't block the install
4773+ # this isn't really a pass/fail test, as it depends
4774+ # on the environment to which
4775+ # the charm is deployed, and doesn't block the install
4776 print('Unable to set fs.file value (perhaps on local provider?).')
4777
4778 output, code = ibm_mq_unit.run('sysctl -p')
4779
4780=== removed file 'tests/bundles.yaml'
4781--- tests/bundles.yaml 2015-07-09 18:38:01 +0000
4782+++ tests/bundles.yaml 1970-01-01 00:00:00 +0000
4783@@ -1,10 +0,0 @@
4784-ibm-mq-liberty-bundle:
4785- services:
4786- "ibm-mq":
4787- charm: "ibm-mq"
4788- num_units: 1
4789- annotations:
4790- "gui-x": "300"
4791- "gui-y": "300"
4792- relations: []
4793- series: trusty
4794
4795=== added file 'tox.ini'
4796--- tox.ini 1970-01-01 00:00:00 +0000
4797+++ tox.ini 2017-02-15 08:51:47 +0000
4798@@ -0,0 +1,12 @@
4799+[tox]
4800+skipsdist=True
4801+envlist = py34, py35
4802+skip_missing_interpreters = True
4803+
4804+[testenv]
4805+commands = py.test -v
4806+deps =
4807+ -r{toxinidir}/requirements.txt
4808+
4809+[flake8]
4810+exclude=docs
4811
4812=== added directory 'wheelhouse'
4813=== added file 'wheelhouse/Jinja2-2.8.tar.gz'
4814Binary files wheelhouse/Jinja2-2.8.tar.gz 1970-01-01 00:00:00 +0000 and wheelhouse/Jinja2-2.8.tar.gz 2017-02-15 08:51:47 +0000 differ
4815=== added file 'wheelhouse/MarkupSafe-0.23.tar.gz'
4816Binary files wheelhouse/MarkupSafe-0.23.tar.gz 1970-01-01 00:00:00 +0000 and wheelhouse/MarkupSafe-0.23.tar.gz 2017-02-15 08:51:47 +0000 differ
4817=== added file 'wheelhouse/PyYAML-3.12.tar.gz'
4818Binary files wheelhouse/PyYAML-3.12.tar.gz 1970-01-01 00:00:00 +0000 and wheelhouse/PyYAML-3.12.tar.gz 2017-02-15 08:51:47 +0000 differ
4819=== added file 'wheelhouse/Tempita-0.5.2.tar.gz'
4820Binary files wheelhouse/Tempita-0.5.2.tar.gz 1970-01-01 00:00:00 +0000 and wheelhouse/Tempita-0.5.2.tar.gz 2017-02-15 08:51:47 +0000 differ
4821=== added file 'wheelhouse/charmhelpers-0.9.1.tar.gz'
4822Binary files wheelhouse/charmhelpers-0.9.1.tar.gz 1970-01-01 00:00:00 +0000 and wheelhouse/charmhelpers-0.9.1.tar.gz 2017-02-15 08:51:47 +0000 differ
4823=== added file 'wheelhouse/charms.reactive-0.4.5.tar.gz'
4824Binary files wheelhouse/charms.reactive-0.4.5.tar.gz 1970-01-01 00:00:00 +0000 and wheelhouse/charms.reactive-0.4.5.tar.gz 2017-02-15 08:51:47 +0000 differ
4825=== added file 'wheelhouse/netaddr-0.7.18.tar.gz'
4826Binary files wheelhouse/netaddr-0.7.18.tar.gz 1970-01-01 00:00:00 +0000 and wheelhouse/netaddr-0.7.18.tar.gz 2017-02-15 08:51:47 +0000 differ
4827=== added file 'wheelhouse/pip-8.1.2.tar.gz'
4828Binary files wheelhouse/pip-8.1.2.tar.gz 1970-01-01 00:00:00 +0000 and wheelhouse/pip-8.1.2.tar.gz 2017-02-15 08:51:47 +0000 differ
4829=== added file 'wheelhouse/pyaml-16.9.0.tar.gz'
4830Binary files wheelhouse/pyaml-16.9.0.tar.gz 1970-01-01 00:00:00 +0000 and wheelhouse/pyaml-16.9.0.tar.gz 2017-02-15 08:51:47 +0000 differ
4831=== added file 'wheelhouse/six-1.10.0.tar.gz'
4832Binary files wheelhouse/six-1.10.0.tar.gz 1970-01-01 00:00:00 +0000 and wheelhouse/six-1.10.0.tar.gz 2017-02-15 08:51:47 +0000 differ

Subscribers

People subscribed via source and target branches

to all changes: