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
=== added file 'Makefile'
--- Makefile 1970-01-01 00:00:00 +0000
+++ Makefile 2017-02-15 08:51:47 +0000
@@ -0,0 +1,24 @@
1#!/usr/bin/make
2
3all: lint unit_test
4
5
6.PHONY: clean
7clean:
8 @rm -rf .tox
9
10.PHONY: apt_prereqs
11apt_prereqs:
12 @# Need tox, but don't install the apt version unless we have to (don't want to conflict with pip)
13 @which tox >/dev/null || (sudo apt-get install -y python-pip && sudo pip install tox)
14
15.PHONY: lint
16lint: apt_prereqs
17 @tox --notest
18 @PATH=.tox/py34/bin:.tox/py35/bin flake8 $(wildcard hooks reactive lib unit_tests tests)
19 @charm proof
20
21.PHONY: unit_test
22unit_test: apt_prereqs
23 @echo Starting tests...
24 tox
025
=== modified file 'README.md'
--- README.md 2015-07-09 18:38:01 +0000
+++ README.md 2017-02-15 08:51:47 +0000
@@ -1,157 +1,144 @@
11## Charm for IBM MQ
2Charm for IBM MQ2
33### Overview
4Overview
5--------
64
7IBM MQ5IBM MQ
86
9IBM MQ provides for messaging services to transport multiple types of data.7IBM MQ provides messaging services to transport multiple types of data.
10MQ Runtime, Server and Sample components from IBM MQ product8MQ Runtime, Server and Sample components from IBM MQ product can be deployed using this charm.
11can be deployed using this charm.
129
1310
14This charm installs IBM MQ product and configures it's IP Address and default11This charm installs IBM MQ product and configures it's IP Address and default
15port number (1414). MQ Administrators can setup queue managers12port number (1414). MQ Administrators can setup queue managers
16and queues. This will allow IBM MQ client application to transmit/receive messages.13and queues. This will allow IBM MQ client application to transmit/receive messages.
1714
18More information on IBM MQ available at the [Product Page][product-page]15More 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)
19and [IBM Knowledge Center][mq-v8-info]16
2017### Usage
21Usage18
22-----19Download your licensed IBM MQ software and host it in a way that can be downloaded using curl.
2320curl-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/)
24Download (see below) your licensed IBM MQ 8.0.0.2 software and copy it to the21
25`files/archives` directory of MQ charm.22### Deploy
2623
27For e.g:24Run the following commands to deploy this charm:
2825
29 * Go to the `files/archives` directory.
30
31 * Copy the `WS_MQ_8.0.0.2_FOR_LIN_ON_LE_POWER.tar.gz` file to this directory for
32 `POWER LE` base machines. For `x86_64`, use `WS_MQ_V8.0.0.2_LINUX_ON_X86_64_I.tar.gz`.
33
34 * The file name you have could be different from the example above (but with the
35 `.gz` extension). Ensure a backup copy of the file.
36
37To install the downloaded binaries you must agree to the IBM license. The license
38file(s) can be found under the `files/archives/licenses` directory on extraction
39of IBM MQ software using the following command:
40
41 tar xvfz *.gz --strip-components=1
42
43The license can also be viewed by running the following command after extracting
44IBM MQ to the files/archives directory:
45
46 ./mqlicense.sh -text_only
47
48Run the following to deploy this charm:
49
50 juju deploy ibm-mq26 juju deploy ibm-mq
5127
52At this point the charm will wait until the user accepts the license. If you28Here the charm will wait until the user accepts the license. If you agree to the license, run the following command.
53agree to the license, run the following command:29
5430 juju set-config ibm-mq license_accepted="True"
55 juju set ibm-mq accept-ibm-mq-license=True31
32IBM 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).
33
34We have to provide values for curl-url and curl-opt to download the product.
35curl-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:
36
37 juju set-config ibm-mq curl_url="<hostname>/<package_dir><package_name>?<sha512=value>"
38 juju set-config ibm-mq curl_opts="-u <username>:<password>"
39
40
41For eg:
42 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"
43
44 juju set-config ibm-mq curl_opts="-u root:root123"
45
46Setting the license-accepted to False will uninstall the product:
47
48 juju set-config ibm-mq license_accepted="False"
49
50
51### Update the following system configuration manually:
5652
57In order to create MQ objects like queue, it is required that some53In order to create MQ objects like queue, it is required that some
58of the system configurations be updated. Details provided at54 of the system configurations be updated. Details provided at
59[IBM Knowledge Center][mq-config-info].55 [IBM Knowledge Center][mq-config-info].
60
61The Charm updates the following system configuration:
6256
63 * In `/etc/sysctl.conf`:57 * In `/etc/sysctl.conf`:
6458
65 fs.file-max = 52428859 fs.file-max = 524288
66 kernel.shmmax = 26843545660 kernel.shmmax = 268435456
6761
68 * In `/etc/security/limits.conf`:62### Relation with other services:
63
64 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'.
65
66 juju add-relation ibm-mq:messaging ibm-was-base:wasmessaging
67 or
68
69 juju add-relation ibm-mq ibm-was-base
70
71IBM-MQ sets Queue Manager name, Queue Name, IP Address and Port for the connected services.
72 It creates a Queue Manager for the consumer application and a local queue i.e QUEUE1 for above Queue Manager.
73 But as a Queue Name it sets a filename where MQSC Commands can be edited for further operation.
74
75on 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.
76
77 juju remove-relation ibm-mq:messaging ibm-was-base:wasmessaging
78or
79
80 juju remove-relation ibm-mq ibm-was-base
81##### The Charm updates the following system configuration:
82
83In order to create MQ objects like queue, it is required that some
84of the system configurations be updated. Details provided at
85[IBM Knowledge Center][mq-config-info].
86
87 * Adds user `ubuntu` to group `mqm`.
88
89 * In `/etc/security/limits.conf` if hard nofile and soft nofile limit for user `mqm` are less than 10240:
6990
70 mqm hard nofile 1024091 mqm hard nofile 10240
71 mqm soft nofile 1024092 mqm soft nofile 10240
7293
73 * Adds user `ubuntu` to group `mqm`.94 * Updates `/etc/pam.d/common-session` file to take the above configurations to take effect
7495
75Verify these changes have been made successfully if MQ throws up any errors.96Verify these changes have been made successfully if MQ throws up any errors.
7697
77Configuration98
78-------------99#### Configuration
79100
80`accept-ibm-mq-license`101**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.
81Before you can use or install IBM MQ,102
82you must accept the terms of International License103 **curl_url**
83Agreement for Non-Warranted Programs and additional license information.104
84Please read the license agreement carefully.105 For downloading package we need to set curl_url with following options.
85106 - *hostname* - The web server host name from which IBM MQ installation packages can be downloaded.
86The license file(s) can be found under the `files/archives` directory after107 - *package_dir* - The package directory path in the web server.
87downloading and extracting IBM MQ there. The license can also be viewed by108 - *package name* - The IBM MQ Package name.
88running the following command after extracting IBM MQ to the `files/archives`109 - *checksum value* - Checksum value to check integrity of IBM MQ package. The Charm
89directory:110 uses sha512sum to check the integrity. If empty, it does not carry out the
90 ./mqlicense.sh -text_only111 integrity check. Use `sha512sum <Package Location/Package Name>`
91112 to find out Checksum value for downloaded package.
92Set the value of accept-ibm-mq-license to True if you agree to IBM MQ license.113
93114**curl_opts**
94**The IBM MQ software cannot be installed until the terms and115
95conditions are accepted. The charm will not function correctly until the116 We need to set curl_opts with following options. - username - User name of the webserver/sftp host. - password - Password of the webserver/sftp host.
96this configuration option is set to True.**117
97118##### Known Limitations:
98Known Limitations
99-----------------
100119
101Local providers does not allow updates to system configuration using sysctl120Local providers does not allow updates to system configuration using sysctl
102command. These configurations will need be done manually for MQ to function.121command. These configurations will need be done manually for MQ to function.
103The required configurations are provided in this document.122The required configurations are provided in this document.
104123
105IBM MQ Information124### IBM MQ Information
106-------------------125
107126(1) General Information
108### (1) General Information127Details about MQ available [here](http://www-01.ibm.com/software/integration/wmq/library/).
109128Details 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).
110Details about MQ available [here][mq-info].129
111130(2) Download Information
112Details about MQ Version 8.0 available at [IBM Knowledge Center][mq-v8-info].131Information 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/).
113132
114### (2) Download Information133A 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.
115134A 90 days trial version is available [here](http://www-03.ibm.com/software/products/en/ibm-mq).
116Information on procuring MQ product is available at the [Product Page][product-page]135
117and at the [Passport Advantage Site][passport].136(3) License Information
118137License 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.
119A development version for `x86_64` is available [here][dev-download].138
120After clicking on the above link and if you agree to the license, click on139./mqlicense.sh -text_only
121'I agree' link to download the IBM MQ package.140
122141License 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).
123A 90 days trial version is available [here][trial-download].142
124143Contact Information
125### (3) License Information144For issues with this charm, please contact jujusupp@us.ibm.com
126
127License information for IBM MQ is available as part of the downloaded product
128package. The license can be viewed by running the following command after
129extracting IBM MQ:
130
131 ./mqlicense.sh -text_only
132
133License for the development version is available
134[here][license].
135
136# Contact Information
137
138For issues with this charm, please contact <jujusupp@us.ibm.com>
139
140
141<!-- Links -->
142
143[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"
144
145[product-page]: http://www-03.ibm.com/software/products/en/ibm-mq "MQ Product Page"
146
147[mq-config-info]: http://www-01.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.ins.doc/q008550_.htm?lang=en
148
149[mq-info]: http://www-01.ibm.com/software/integration/wmq/library/ "MQ Info"
150
151[passport]: http://www-01.ibm.com/software/passportadvantage/ "Passport Advantage"
152
153[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"
154
155[trial-download]: http://www-03.ibm.com/software/products/en/ibm-mq "Trial Download"
156
157[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"
158145
=== added directory 'bin'
=== added file 'bin/layer_option'
--- bin/layer_option 1970-01-01 00:00:00 +0000
+++ bin/layer_option 2017-02-15 08:51:47 +0000
@@ -0,0 +1,24 @@
1#!/usr/bin/env python3
2
3import sys
4sys.path.append('lib')
5
6import argparse
7from charms.layer import options
8
9
10parser = argparse.ArgumentParser(description='Access layer options.')
11parser.add_argument('section',
12 help='the section, or layer, the option is from')
13parser.add_argument('option',
14 help='the option to access')
15
16args = parser.parse_args()
17value = options(args.section).get(args.option, '')
18if isinstance(value, bool):
19 sys.exit(0 if value else 1)
20elif isinstance(value, list):
21 for val in value:
22 print(val)
23else:
24 print(value)
025
=== added file 'config.yaml'
--- config.yaml 1970-01-01 00:00:00 +0000
+++ config.yaml 2017-02-15 08:51:47 +0000
@@ -0,0 +1,55 @@
1"options":
2 "extra_packages":
3 "description": "Space separated list of extra deb packages to install.\n"
4 "type": "string"
5 "default": ""
6 "package_status":
7 "default": "install"
8 "type": "string"
9 "description": "The status of service-affecting packages will be set to this value\
10 \ in the dpkg database. Valid values are \"install\" and \"hold\".\n"
11 "install_sources":
12 "description": "List of extra apt sources, per charm-helpers standard format (a\
13 \ yaml list of strings encoded as a string). Each source may be either a line\
14 \ that can be added directly to sources.list(5), or in the form ppa:<user>/<ppa-name>\
15 \ for adding Personal Package Archives, or a distribution component to enable.\n"
16 "type": "string"
17 "default": ""
18 "install_keys":
19 "description": "List of signing keys for install_sources package sources, per\
20 \ charmhelpers standard format (a yaml list of strings encoded as a string).\
21 \ The keys should be the full ASCII armoured GPG public keys. While GPG key\
22 \ ids are also supported and looked up on a keyserver, operators should be aware\
23 \ that this mechanism is insecure. null can be used if a standard package signing\
24 \ key is used that will already be installed on the machine, and for PPA sources\
25 \ where the package signing key is securely retrieved from Launchpad.\n"
26 "type": "string"
27 "default": ""
28 "curl_url":
29 "type": "string"
30 "default": ""
31 "description": |
32 Location of the IBM product installation file(s). This should be a URL
33 that curl can use to download files. Multiple URLs should be separated
34 by a space. NOTE: cryptographic verification is required and must be
35 specified as part of the URL query string with the key a valid hash
36 algorithms md5, sha256, or sha512, and the the checksum value itself
37 (http://<url>?[md5|sha256|sha512]=<checksum>).
38 For example:
39 'http://example.com/file.tgz?sha256=<sum>'
40 'sftp://example.com/file1.tgz?md5=<sum> ftp://example.com/file2.tgz?md5=<sum>'
41 "curl_opts":
42 "type": "string"
43 "default": ""
44 "description": |
45 The options passed to the 'curl' command when fetching files from
46 curl_url. For example:
47 '-u <user:password>'
48 "license_accepted":
49 "type": "boolean"
50 "default": !!bool "false"
51 "description": |
52 Some IBM charms require acceptance of a license before installation
53 can proceed. If required, setting this option to True indicates that you
54 have read and accepted the IBM terms and conditions found in the license
55 file referenced by the charm.
056
=== removed file 'config.yaml'
--- config.yaml 2015-05-03 16:45:29 +0000
+++ config.yaml 1970-01-01 00:00:00 +0000
@@ -1,12 +0,0 @@
1options:
2 accept-ibm-mq-license:
3 type: boolean
4 default: False
5 description: |
6 The MQ software can only be used by
7 accepting the terms and conditions for IBM MQ License.
8 Setting this option to True
9 indicates that you have read and accepted the IBM MQ terms and
10 conditions found in the license file. The details about accessing
11 the license(s) can be found in the README.
12 ** Do not set to False if a relation exists with this charm **
130
=== modified file 'copyright'
--- copyright 2015-03-11 19:52:30 +0000
+++ copyright 2017-02-15 08:51:47 +0000
@@ -1,4 +1,4 @@
1Copyright 2015 IBM Corporation1Copyright 2016 IBM Corporation
22
3This Charm is licensed under the Apache License, Version 2.0 (the "License");3This Charm is licensed under the Apache License, Version 2.0 (the "License");
4you may not use this file except in compliance with the License.4you may not use this file except in compliance with the License.
55
=== added directory 'deps'
=== added directory 'deps/layer'
=== added directory 'deps/layer/layer-apt'
=== added file 'deps/layer/layer-apt/.gitignore'
--- deps/layer/layer-apt/.gitignore 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-apt/.gitignore 2017-02-15 08:51:47 +0000
@@ -0,0 +1,2 @@
1*~
2*.pyc
03
=== added file 'deps/layer/layer-apt/README.md'
--- deps/layer/layer-apt/README.md 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-apt/README.md 2017-02-15 08:51:47 +0000
@@ -0,0 +1,195 @@
1# Apt layer
2
3The Apt layer for Juju enables layered charms to more easily deal with
4deb packages and apt sources in a simple and efficient manner. It
5provides consistent configuration for operators, allowing them to
6easily specify custom apt sources and additional debs required for
7their particular installations.
8
9## Configuration
10
11The charm may provide defaults for these service configuration
12(config.yaml) options, and the operator may override them as required.
13
14* `extra_packages`
15
16 A space separated list of additional deb packages to install on
17 each unit.
18
19* `package_status`
20
21 'install' or 'hold'. When set to hold, packages installed using
22 the Apt layer API will be pinned, so that they will not be
23 automatically upgraded when package updates are performed. 'hold'
24 is particularly useful for allowing a service such as Landscape
25 to automatically apply security updates to most of the system,
26 whilst holding back any potentially service affecting updates.
27
28* `install_sources`
29
30 A list of apt sources containing the packages that need to be installed.
31 Each source may be either a line that can be added directly to
32 sources.list(5), or in the form ppa:<user>/<ppa-name> for adding
33 Personal Package Archives, or a distribution component to enable.
34 The list is a yaml list, encoded as a string. The nicest way of
35 declaring this in a yaml file looks like the following (in particular,
36 the | character indicates that the value is a multiline string):
37
38 ```yaml
39 install_sources: |
40 - ppa:stub/cassandra
41 - deb http://www.apache.org/dist/cassandra/debian 21x main
42 ```
43
44* `install_keys`
45
46 A list of GPG signing keys to accept. There needs to be one entry
47 per entry in install_sources. null may be used if no keep is
48 needed, which is the case for PPAs and for the standard Ubuntu
49 archives. Keys should be full ASCII armoured GPG public keys.
50 GPG key ids are also accepted, but in most environments this
51 mechanism is not secure. The install_keys list, like
52 install_sources, must also be a yaml formatted list encoded as
53 a string:
54
55 ```yaml
56 install_keys: |
57 - null
58 - |
59 -----BEGIN PGP PUBLIC KEY BLOCK-----
60 Version: GnuPG v1
61
62 mQINBFQJvgUBEAC0KcYCTj0hd15p4fiXBsbob0sKgsvN5Lm7N9jzJWlGshJ0peMi
63 kH8YhDXw5Lh+mPEHksL7t1L8CIr1a+ntns/Opt65ZPO38ENVkOqEVAn9Z5sIoZsb
64 AUeLlJzSeRLTKhcOugK7UcsQD2FHnMBJz50bxis9X7pjmnc/tWpjAGJfaWdjDIo=
65 =yiQ4
66 -----END PGP PUBLIC KEY BLOCK-----
67 ```
68
69## Usage
70
71Queue packages for installation, and have handlers waiting for
72these packages to finish being installed:
73
74```python
75import charms.apt
76
77@hook('install')
78def install():
79 charms.apt.queue_install(['git'])
80
81@when_not('apt.installed.gnupg')
82def install_gnupg():
83 charms.apt.queue_install(['gnupg'])
84
85@when('apt.installed.git')
86@when('apt.installed.gnupg')
87def grabit():
88 clone_repo()
89 validate_repo()
90```
91
92### API
93
94Several methods are exposed in the charms.apt Python package.
95
96* `add_source(source, key=None)`
97
98 Add an apt source.
99
100 A source may be either a line that can be added directly to
101 sources.list(5), or in the form ppa:<user>/<ppa-name> for adding
102 Personal Package Archives, or a distribution component to enable.
103
104 The package signing key should be an ASCII armoured GPG key. While
105 GPG key ids are also supported, the retrieval mechanism is insecure.
106 There is no need to specify the package signing key for PPAs or for
107 the main Ubuntu archives.
108
109 It is preferable if charms do not call this directly to hard
110 coded apt sources, but instead have these sources listed
111 as defaults in the install_sources config option. This allows
112 operators to mirror your packages to internal archives and
113 deploy your charm in environments without network access.
114
115 Sets the `apt.needs_update` reactive state.
116
117* `queue_install(packages, options=None)`
118
119 Queue one or more deb packages for install. The actual package
120 installation will be performed later by a handler in the
121 apt layer. The `apt.installed.{name}` state will be set once
122 the package installed (one state for each package).
123
124 If a package has already been installed it will not be reinstalled.
125
126 If a package has already been queued it will not be requeued, and
127 the install options will not be changed.
128
129* `installed()`
130
131 Returns the set of deb packages installed by this layer.
132
133* `purge(packages)`
134
135 Purge one or more deb packages from the system
136
137
138### Extras
139
140These methods are called automatically by the reactive framework as
141reactive state demands. However, you can also invoke them directly
142if you want the operation done right now.
143
144* `update()`
145
146 Update the apt cache. Removes the `apt.needs_update` state.
147
148
149* `install_queued()`
150
151 Installs deb packages queued for installation. On success, removes
152 the `apt.queued_installs` state, sets the `apt.installed.{packagename}`
153 state for each installed package, and returns True. On failure,
154 sets the unit workload status to blocked and returns False.
155 The package installs remain queued.
156
157
158### Layer Options
159
160Packages can be specified at charm-build time in `layer.yaml`. List the
161packages in the 'basic' or 'apt' sections.
162
163```yaml
164includes:
165 - layer:basic
166 - layer:apt
167options:
168 basic:
169 packages:
170 - python3-psycopg2
171 apt:
172 packages:
173 - git
174 - bzr
175```
176
177Packages required to import your Python reactive handlers should go
178under 'basic'. These get installed by the base layer very early during
179charm bootstrap, and only packages available in the main Ubuntu archive
180can go here. Other packages should go under 'apt'. These will be
181installed later, after custom apt sources such as PPAs have been added
182from the `install_sources` configuration option.
183
184
185## Support
186
187This layer is maintained on Launchpad by
188Stuart Bishop (stuart.bishop@canonical.com).
189
190Code is available using git at git+ssh://git.launchpad.net/layer-apt.
191
192Bug reports can be made at https://bugs.launchpad.net/layer-apt.
193
194Queries and comments can be made on the Juju mailing list, Juju IRC
195channels, or at https://answers.launchpad.net/layer-apt.
0196
=== added file 'deps/layer/layer-apt/config.yaml'
--- deps/layer/layer-apt/config.yaml 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-apt/config.yaml 2017-02-15 08:51:47 +0000
@@ -0,0 +1,34 @@
1options:
2 extra_packages:
3 description: >
4 Space separated list of extra deb packages to install.
5 type: string
6 default: ""
7 package_status:
8 default: "install"
9 type: string
10 description: >
11 The status of service-affecting packages will be set to this
12 value in the dpkg database. Valid values are "install" and "hold".
13 install_sources:
14 description: >
15 List of extra apt sources, per charm-helpers standard
16 format (a yaml list of strings encoded as a string). Each source
17 may be either a line that can be added directly to
18 sources.list(5), or in the form ppa:<user>/<ppa-name> for adding
19 Personal Package Archives, or a distribution component to enable.
20 type: string
21 default: ""
22 install_keys:
23 description: >
24 List of signing keys for install_sources package sources, per
25 charmhelpers standard format (a yaml list of strings encoded as
26 a string). The keys should be the full ASCII armoured GPG public
27 keys. While GPG key ids are also supported and looked up on a
28 keyserver, operators should be aware that this mechanism is
29 insecure. null can be used if a standard package signing key is
30 used that will already be installed on the machine, and for PPA
31 sources where the package signing key is securely retrieved from
32 Launchpad.
33 type: string
34 default: ""
035
=== added file 'deps/layer/layer-apt/copyright'
--- deps/layer/layer-apt/copyright 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-apt/copyright 2017-02-15 08:51:47 +0000
@@ -0,0 +1,15 @@
1Copyright 2015-2016 Canonical Ltd.
2
3This file is part of the Apt layer for Juju.
4
5This program is free software: you can redistribute it and/or modify
6it under the terms of the GNU General Public License version 3, as
7published by the Free Software Foundation.
8
9This program is distributed in the hope that it will be useful, but
10WITHOUT ANY WARRANTY; without even the implied warranties of
11MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12PURPOSE. See the GNU General Public License for more details.
13
14You should have received a copy of the GNU General Public License
15along with this program. If not, see <http://www.gnu.org/licenses/>.
016
=== added file 'deps/layer/layer-apt/layer.yaml'
--- deps/layer/layer-apt/layer.yaml 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-apt/layer.yaml 2017-02-15 08:51:47 +0000
@@ -0,0 +1,5 @@
1defines:
2 packages:
3 type: array
4 default: []
5 description: Additional packages to be installed at time of bootstrap
06
=== added directory 'deps/layer/layer-apt/lib'
=== added directory 'deps/layer/layer-apt/lib/charms'
=== added file 'deps/layer/layer-apt/lib/charms/__init__.py'
--- deps/layer/layer-apt/lib/charms/__init__.py 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-apt/lib/charms/__init__.py 2017-02-15 08:51:47 +0000
@@ -0,0 +1,2 @@
1from pkgutil import extend_path
2__path__ = extend_path(__path__, __name__)
03
=== added file 'deps/layer/layer-apt/lib/charms/apt.py'
--- deps/layer/layer-apt/lib/charms/apt.py 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-apt/lib/charms/apt.py 2017-02-15 08:51:47 +0000
@@ -0,0 +1,182 @@
1# Copyright 2015-2016 Canonical Ltd.
2#
3# This file is part of the Apt layer for Juju.
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 3, as
7# published by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17'''
18charms.reactive helpers for dealing with deb packages.
19
20Add apt package sources using add_source(). Queue deb packages for
21installation with install(). Configure and work with your software
22once the apt.installed.{packagename} state is set.
23'''
24import itertools
25import subprocess
26
27from charmhelpers import fetch
28from charmhelpers.core import hookenv, unitdata
29from charms import reactive
30
31
32__all__ = ['add_source', 'update', 'queue_install', 'install_queued',
33 'installed', 'purge', 'ensure_package_status']
34
35
36def add_source(source, key=None):
37 '''Add an apt source.
38
39 Sets the apt.needs_update state.
40
41 A source may be either a line that can be added directly to
42 sources.list(5), or in the form ppa:<user>/<ppa-name> for adding
43 Personal Package Archives, or a distribution component to enable.
44
45 The package signing key should be an ASCII armoured GPG key. While
46 GPG key ids are also supported, the retrieval mechanism is insecure.
47 There is no need to specify the package signing key for PPAs or for
48 the main Ubuntu archives.
49 '''
50 # Maybe we should remember which sources have been added already
51 # so we don't waste time re-adding them. Is this time significant?
52 fetch.add_source(source, key)
53 reactive.set_state('apt.needs_update')
54
55
56def queue_install(packages, options=None):
57 """Queue one or more deb packages for install.
58
59 The `apt.installed.{name}` state is set once the package is installed.
60
61 If a package has already been installed it will not be reinstalled.
62
63 If a package has already been queued it will not be requeued, and
64 the install options will not be changed.
65
66 Sets the apt.queued_installs state.
67 """
68 if isinstance(packages, str):
69 packages = [packages]
70 # Filter installed packages.
71 store = unitdata.kv()
72 queued_packages = store.getrange('apt.install_queue.', strip=True)
73 packages = {package: options for package in packages
74 if not (package in queued_packages or
75 reactive.helpers.is_state('apt.installed.' + package))}
76 if packages:
77 unitdata.kv().update(packages, prefix='apt.install_queue.')
78 reactive.set_state('apt.queued_installs')
79
80
81def installed():
82 '''Return the set of deb packages completed install'''
83 return set(state.split('.', 2)[2] for state in reactive.bus.get_states()
84 if state.startswith('apt.installed.'))
85
86
87def purge(packages):
88 """Purge one or more deb packages from the system"""
89 fetch.apt_purge(packages, fatal=True)
90 store = unitdata.kv()
91 store.unsetrange(packages, prefix='apt.install_queue.')
92 for package in packages:
93 reactive.remove_state('apt.installed.{}'.format(package))
94
95
96def update():
97 """Update the apt cache.
98
99 Removes the apt.needs_update state.
100 """
101 status_set(None, 'Updating apt cache')
102 fetch.apt_update(fatal=True) # Friends don't let friends set fatal=False
103 reactive.remove_state('apt.needs_update')
104
105
106def install_queued():
107 '''Installs queued deb packages.
108
109 Removes the apt.queued_installs state and sets the apt.installed state.
110
111 On failure, sets the unit's workload state to 'blocked' and returns
112 False. Package installs remain queued.
113
114 On success, sets the apt.installed.{packagename} state for each
115 installed package and returns True.
116 '''
117 store = unitdata.kv()
118 queue = sorted((options, package)
119 for package, options in store.getrange('apt.install_queue.',
120 strip=True).items())
121
122 installed = set()
123 for options, batch in itertools.groupby(queue, lambda x: x[0]):
124 packages = [b[1] for b in batch]
125 try:
126 status_set(None, 'Installing {}'.format(','.join(packages)))
127 fetch.apt_install(packages, options, fatal=True)
128 store.unsetrange(packages, prefix='apt.install_queue.')
129 installed.update(packages)
130 except subprocess.CalledProcessError:
131 status_set('blocked',
132 'Unable to install packages {}'
133 .format(','.join(packages)))
134 return False # Without setting reactive state.
135
136 for package in installed:
137 reactive.set_state('apt.installed.{}'.format(package))
138
139 reactive.remove_state('apt.queued_installs')
140 return True
141
142
143def ensure_package_status():
144 '''Hold or unhold packages per the package_status configuration option.
145
146 All packages installed using this module and handlers are affected.
147
148 An mechanism may be added in the future to override this for a
149 subset of installed packages.
150 '''
151 packages = installed()
152 if not packages:
153 return
154 config = hookenv.config()
155 package_status = config['package_status']
156 changed = reactive.helpers.data_changed('apt.package_status',
157 (package_status, sorted(packages)))
158 if changed:
159 if package_status == 'hold':
160 hookenv.log('Holding packages {}'.format(','.join(packages)))
161 fetch.apt_hold(packages)
162 else:
163 hookenv.log('Unholding packages {}'.format(','.join(packages)))
164 fetch.apt_unhold(packages)
165 reactive.remove_state('apt.needs_hold')
166
167
168def status_set(state, message):
169 """Set the unit's workload status.
170
171 Set state == None to keep the same state and just change the message.
172 """
173 if state is None:
174 state = hookenv.status_get()[0]
175 if state == 'unknown':
176 state = 'maintenance' # Guess
177 if state in ('error', 'blocked'):
178 lvl = hookenv.WARNING
179 else:
180 lvl = hookenv.INFO
181 hookenv.status_set(state, message)
182 hookenv.log('{}: {}'.format(state, message), lvl)
0183
=== added directory 'deps/layer/layer-apt/reactive'
=== added file 'deps/layer/layer-apt/reactive/apt.py'
--- deps/layer/layer-apt/reactive/apt.py 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-apt/reactive/apt.py 2017-02-15 08:51:47 +0000
@@ -0,0 +1,131 @@
1# Copyright 2015-2016 Canonical Ltd.
2#
3# This file is part of the Apt layer for Juju.
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 3, as
7# published by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17'''
18charms.reactive helpers for dealing with deb packages.
19
20Add apt package sources using add_source(). Queue deb packages for
21installation with install(). Configure and work with your software
22once the apt.installed.{packagename} state is set.
23'''
24import subprocess
25
26from charmhelpers import fetch
27from charmhelpers.core import hookenv
28from charmhelpers.core.hookenv import WARNING
29from charms import layer
30from charms import reactive
31from charms.reactive import when, when_not
32
33import charms.apt
34
35
36@when('apt.needs_update')
37def update():
38 charms.apt.update()
39
40
41@when('apt.queued_installs')
42@when_not('apt.needs_update')
43def install_queued():
44 charms.apt.install_queued()
45
46
47@when_not('apt.queued_installs')
48def ensure_package_status():
49 charms.apt.ensure_package_status()
50
51
52def filter_installed_packages(packages):
53 # Don't use fetch.filter_installed_packages, as it depends on python-apt
54 # and not available if the basic layer's use_site_packages option is off
55 # TODO: Move this to charm-helpers.fetch
56 cmd = ['dpkg-query', '--show', r'--showformat=${Package}\n']
57 installed = set(subprocess.check_output(cmd,
58 universal_newlines=True).split())
59 return set(packages) - installed
60
61
62def clear_removed_package_states():
63 """On hook startup, clear install states for removed packages."""
64 removed = filter_installed_packages(charms.apt.installed())
65 if removed:
66 hookenv.log('{} missing packages ({})'.format(len(removed),
67 ','.join(removed)),
68 WARNING)
69 for package in removed:
70 reactive.remove_state('apt.installed.{}'.format(package))
71
72
73def configure_sources():
74 """Add user specified package sources from the service configuration.
75
76 See charmhelpers.fetch.configure_sources for details.
77 """
78 config = hookenv.config()
79
80 # We don't have enums, so we need to validate this ourselves.
81 package_status = config.get('package_status')
82 if package_status not in ('hold', 'install'):
83 charms.apt.status_set('blocked',
84 'Unknown package_status {}'
85 ''.format(package_status))
86 # Die before further hooks are run. This isn't very nice, but
87 # there is no other way to inform the operator that they have
88 # invalid configuration.
89 raise SystemExit(0)
90
91 sources = config.get('install_sources')
92 keys = config.get('install_keys')
93 if reactive.helpers.data_changed('apt.configure_sources', (sources, keys)):
94 fetch.configure_sources(update=False,
95 sources_var='install_sources',
96 keys_var='install_keys')
97 reactive.set_state('apt.needs_update')
98
99 extra_packages = sorted(config.get('extra_packages', '').split())
100 if extra_packages:
101 charms.apt.queue_install(extra_packages)
102
103
104def queue_layer_packages():
105 """Add packages listed in build-time layer options."""
106 # Both basic and apt layer. basic layer will have already installed
107 # its defined packages, but rescheduling it here gets the apt layer
108 # state set and they will pinned as any other apt layer installed
109 # package.
110 opts = layer.options()
111 for section in ['basic', 'apt']:
112 if section in opts and 'packages' in opts[section]:
113 charms.apt.queue_install(opts[section]['packages'])
114
115
116# Per https://github.com/juju-solutions/charms.reactive/issues/33,
117# this module may be imported multiple times so ensure the
118# initialization hook is only registered once. I have to piggy back
119# onto the namespace of a module imported before reactive discovery
120# to do this.
121if not hasattr(reactive, '_apt_registered'):
122 # We need to register this to run every hook, not just during install
123 # and config-changed, to protect against race conditions. If we don't
124 # do this, then the config in the hook environment may show updates
125 # to running hooks well before the config-changed hook has been invoked
126 # and the intialization provided an opertunity to be run.
127 hookenv.atstart(hookenv.log, 'Initializing Apt Layer')
128 hookenv.atstart(clear_removed_package_states)
129 hookenv.atstart(configure_sources)
130 hookenv.atstart(queue_layer_packages)
131 reactive._apt_registered = True
0132
=== added directory 'deps/layer/layer-basic'
=== added file 'deps/layer/layer-basic/.gitignore'
--- deps/layer/layer-basic/.gitignore 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-basic/.gitignore 2017-02-15 08:51:47 +0000
@@ -0,0 +1,5 @@
1*.pyc
2*~
3.ropeproject
4.settings
5.tox
06
=== added file 'deps/layer/layer-basic/Makefile'
--- deps/layer/layer-basic/Makefile 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-basic/Makefile 2017-02-15 08:51:47 +0000
@@ -0,0 +1,24 @@
1#!/usr/bin/make
2
3all: lint unit_test
4
5
6.PHONY: clean
7clean:
8 @rm -rf .tox
9
10.PHONY: apt_prereqs
11apt_prereqs:
12 @# Need tox, but don't install the apt version unless we have to (don't want to conflict with pip)
13 @which tox >/dev/null || (sudo apt-get install -y python-pip && sudo pip install tox)
14
15.PHONY: lint
16lint: apt_prereqs
17 @tox --notest
18 @PATH=.tox/py34/bin:.tox/py35/bin flake8 $(wildcard hooks reactive lib unit_tests tests)
19 @charm proof
20
21.PHONY: unit_test
22unit_test: apt_prereqs
23 @echo Starting tests...
24 tox
025
=== added file 'deps/layer/layer-basic/README.md'
--- deps/layer/layer-basic/README.md 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-basic/README.md 2017-02-15 08:51:47 +0000
@@ -0,0 +1,221 @@
1# Overview
2
3This is the base layer for all charms [built using layers][building]. It
4provides all of the standard Juju hooks and runs the
5[charms.reactive.main][charms.reactive] loop for them. It also bootstraps the
6[charm-helpers][] and [charms.reactive][] libraries and all of their
7dependencies for use by the charm.
8
9# Usage
10
11To create a charm layer using this base layer, you need only include it in
12a `layer.yaml` file:
13
14```yaml
15includes: ['layer:basic']
16```
17
18This will fetch this layer from [interfaces.juju.solutions][] and incorporate
19it into your charm layer. You can then add handlers under the `reactive/`
20directory. Note that **any** file under `reactive/` will be expected to
21contain handlers, whether as Python decorated functions or [executables][non-python]
22using the [external handler protocol][].
23
24### Charm Dependencies
25
26Each layer can include a `wheelhouse.txt` file with Python requirement lines.
27For example, this layer's `wheelhouse.txt` includes:
28
29```
30pip>=7.0.0,<8.0.0
31charmhelpers>=0.4.0,<1.0.0
32charms.reactive>=0.1.0,<2.0.0
33```
34
35All of these dependencies from each layer will be fetched (and updated) at build
36time and will be automatically installed by this base layer before any reactive
37handlers are run.
38
39Note that the `wheelhouse.txt` file is intended for **charm** dependencies only.
40That is, for libraries that the charm code itself needs to do its job of deploying
41and configuring the payload. If the payload itself has Python dependencies, those
42should be handled separately, by the charm.
43
44See [PyPI][pypi charms.X] for packages under the `charms.` namespace which might
45be useful for your charm.
46
47### Layer Namespace
48
49Each layer has a reserved section in the `charms.layer.` Python package namespace,
50which it can populate by including a `lib/charms/layer/<layer-name>.py` file or
51by placing files under `lib/charms/layer/<layer-name>/`. (If the layer name
52includes hyphens, replace them with underscores.) These can be helpers that the
53layer uses internally, or it can expose classes or functions to be used by other
54layers to interact with that layer.
55
56For example, a layer named `foo` could include a `lib/charms/layer/foo.py` file
57with some helper functions that other layers could access using:
58
59```python
60from charms.layer.foo import my_helper
61```
62
63### Layer Options
64
65Any layer can define options in its `layer.yaml`. Those options can then be set
66by other layers to change the behavior of your layer. The options are defined
67using [jsonschema][], which is the same way that [action paramters][] are defined.
68
69For example, the `foo` layer could include the following option definitons:
70
71```yaml
72includes: ['layer:basic']
73defines: # define some options for this layer (the layer "foo")
74 enable-bar: # define an "enable-bar" option for this layer
75 description: If true, enable support for "bar".
76 type: boolean
77 default: false
78```
79
80A layer using `foo` could then set it:
81
82```yaml
83includes: ['layer:foo']
84options:
85 foo: # setting options for the "foo" layer
86 enable-bar: true # set the "enable-bar" option to true
87```
88
89The `foo` layer can then use the `charms.layer.options` helper to load the values
90for the options that it defined. For example:
91
92```python
93from charms import layer
94
95@when('state')
96def do_thing():
97 layer_opts = layer.options('foo') # load all of the options for the "foo" layer
98 if layer_opts['enable-bar']: # check the value of the "enable-bar" option
99 hookenv.log("Bar is enabled")
100```
101
102You can also access layer options in other handlers, such as Bash, using
103the command-line interface:
104
105```bash
106. charms.reactive.sh
107
108@when 'state'
109function do_thing() {
110 if layer_option foo enable-bar; then
111 juju-log "Bar is enabled"
112 juju-log "bar-value is: $(layer_option foo bar-value)"
113 fi
114}
115
116reactive_handler_main
117```
118
119Note that options of type `boolean` will set the exit code, while other types
120will be printed out.
121
122# Hooks
123
124This layer provides hooks that other layers can react to using the decorators
125of the [charms.reactive][] library:
126
127 * `config-changed`
128 * `install`
129 * `leader-elected`
130 * `leader-settings-changed`
131 * `start`
132 * `stop`
133 * `upgrade-charm`
134 * `update-status`
135
136Other hooks are not implemented at this time. A new layer can implement storage
137or relation hooks in their own layer by putting them in the `hooks` directory.
138
139**Note:** Because `update-status` is invoked every 5 minutes, you should take
140care to ensure that your reactive handlers only invoke expensive operations
141when absolutely necessary. It is recommended that you use helpers like
142[`@only_once`][], [`@when_file_changed`][], and [`data_changed`][] to ensure
143that handlers run only when necessary.
144
145# Layer Configuration
146
147This layer supports the following options, which can be set in `layer.yaml`:
148
149 * **packages** A list of system packages to be installed before the reactive
150 handlers are invoked.
151
152 * **use_venv** If set to true, the charm dependencies from the various
153 layers' `wheelhouse.txt` files will be installed in a Python virtualenv
154 located at `$CHARM_DIR/../.venv`. This keeps charm dependencies from
155 conflicting with payload dependencies, but you must take care to preserve
156 the environment and interpreter if using `execl` or `subprocess`.
157
158 * **include_system_packages** If set to true and using a venv, include
159 the `--system-site-packages` options to make system Python libraries
160 visible within the venv.
161
162An example `layer.yaml` using these options might be:
163
164```yaml
165includes: ['layer:basic']
166options:
167 basic:
168 packages: ['git']
169 use_venv: true
170 include_system_packages: true
171```
172
173
174# Reactive States
175
176This layer will set the following states:
177
178 * **`config.changed`** Any config option has changed from its previous value.
179 This state is cleared automatically at the end of each hook invocation.
180
181 * **`config.changed.<option>`** A specific config option has changed.
182 **`<option>`** will be replaced by the config option name from `config.yaml`.
183 This state is cleared automatically at the end of each hook invocation.
184
185 * **`config.set.<option>`** A specific config option has a True or non-empty
186 value set. **`<option>`** will be replaced by the config option name from
187 `config.yaml`. This state is cleared automatically at the end of each hook
188 invocation.
189
190 * **`config.default.<option>`** A specific config option is set to its
191 default value. **`<option>`** will be replaced by the config option name
192 from `config.yaml`. This state is cleared automatically at the end of
193 each hook invocation.
194
195An example using the config states would be:
196
197```python
198@when('config.changed.my-opt')
199def my_opt_changed():
200 update_config()
201 restart_service()
202```
203
204
205# Actions
206
207This layer currently does not define any actions.
208
209
210[building]: https://jujucharms.com/docs/devel/authors-charm-building
211[charm-helpers]: https://pythonhosted.org/charmhelpers/
212[charms.reactive]: https://pythonhosted.org/charms.reactive/
213[interfaces.juju.solutions]: http://interfaces.juju.solutions/
214[non-python]: https://pythonhosted.org/charms.reactive/#non-python-reactive-handlers
215[external handler protocol]: https://pythonhosted.org/charms.reactive/charms.reactive.bus.html#charms.reactive.bus.ExternalHandler
216[jsonschema]: http://json-schema.org/
217[action paramters]: https://jujucharms.com/docs/stable/authors-charm-actions
218[pypi charms.X]: https://pypi.python.org/pypi?%3Aaction=search&term=charms.&submit=search
219[`@only_once`]: https://pythonhosted.org/charms.reactive/charms.reactive.decorators.html#charms.reactive.decorators.only_once
220[`@when_file_changed`]: https://pythonhosted.org/charms.reactive/charms.reactive.decorators.html#charms.reactive.decorators.when_file_changed
221[`data_changed`]: https://pythonhosted.org/charms.reactive/charms.reactive.helpers.html#charms.reactive.helpers.data_changed
0222
=== added directory 'deps/layer/layer-basic/bin'
=== added file 'deps/layer/layer-basic/bin/layer_option'
--- deps/layer/layer-basic/bin/layer_option 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-basic/bin/layer_option 2017-02-15 08:51:47 +0000
@@ -0,0 +1,24 @@
1#!/usr/bin/env python3
2
3import sys
4sys.path.append('lib')
5
6import argparse
7from charms.layer import options
8
9
10parser = argparse.ArgumentParser(description='Access layer options.')
11parser.add_argument('section',
12 help='the section, or layer, the option is from')
13parser.add_argument('option',
14 help='the option to access')
15
16args = parser.parse_args()
17value = options(args.section).get(args.option, '')
18if isinstance(value, bool):
19 sys.exit(0 if value else 1)
20elif isinstance(value, list):
21 for val in value:
22 print(val)
23else:
24 print(value)
025
=== added file 'deps/layer/layer-basic/copyright'
--- deps/layer/layer-basic/copyright 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-basic/copyright 2017-02-15 08:51:47 +0000
@@ -0,0 +1,9 @@
1Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0
2
3Files: *
4Copyright: 2015, Canonical Ltd.
5License: GPL-3
6
7License: GPL-3
8 On Debian GNU/Linux system you can find the complete text of the
9 GPL-3 license in '/usr/share/common-licenses/GPL-3'
010
=== added directory 'deps/layer/layer-basic/hooks'
=== added file 'deps/layer/layer-basic/hooks/config-changed'
--- deps/layer/layer-basic/hooks/config-changed 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-basic/hooks/config-changed 2017-02-15 08:51:47 +0000
@@ -0,0 +1,19 @@
1#!/usr/bin/env python3
2
3# Load modules from $CHARM_DIR/lib
4import sys
5sys.path.append('lib')
6
7from charms.layer import basic
8basic.bootstrap_charm_deps()
9basic.init_config_states()
10
11
12# This will load and run the appropriate @hook and other decorated
13# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
14# and $CHARM_DIR/hooks/relations.
15#
16# See https://jujucharms.com/docs/stable/authors-charm-building
17# for more information on this pattern.
18from charms.reactive import main
19main()
020
=== added file 'deps/layer/layer-basic/hooks/hook.template'
--- deps/layer/layer-basic/hooks/hook.template 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-basic/hooks/hook.template 2017-02-15 08:51:47 +0000
@@ -0,0 +1,19 @@
1#!/usr/bin/env python3
2
3# Load modules from $CHARM_DIR/lib
4import sys
5sys.path.append('lib')
6
7from charms.layer import basic
8basic.bootstrap_charm_deps()
9basic.init_config_states()
10
11
12# This will load and run the appropriate @hook and other decorated
13# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
14# and $CHARM_DIR/hooks/relations.
15#
16# See https://jujucharms.com/docs/stable/authors-charm-building
17# for more information on this pattern.
18from charms.reactive import main
19main()
020
=== added file 'deps/layer/layer-basic/hooks/install'
--- deps/layer/layer-basic/hooks/install 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-basic/hooks/install 2017-02-15 08:51:47 +0000
@@ -0,0 +1,19 @@
1#!/usr/bin/env python3
2
3# Load modules from $CHARM_DIR/lib
4import sys
5sys.path.append('lib')
6
7from charms.layer import basic
8basic.bootstrap_charm_deps()
9basic.init_config_states()
10
11
12# This will load and run the appropriate @hook and other decorated
13# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
14# and $CHARM_DIR/hooks/relations.
15#
16# See https://jujucharms.com/docs/stable/authors-charm-building
17# for more information on this pattern.
18from charms.reactive import main
19main()
020
=== added file 'deps/layer/layer-basic/hooks/leader-elected'
--- deps/layer/layer-basic/hooks/leader-elected 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-basic/hooks/leader-elected 2017-02-15 08:51:47 +0000
@@ -0,0 +1,19 @@
1#!/usr/bin/env python3
2
3# Load modules from $CHARM_DIR/lib
4import sys
5sys.path.append('lib')
6
7from charms.layer import basic
8basic.bootstrap_charm_deps()
9basic.init_config_states()
10
11
12# This will load and run the appropriate @hook and other decorated
13# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
14# and $CHARM_DIR/hooks/relations.
15#
16# See https://jujucharms.com/docs/stable/authors-charm-building
17# for more information on this pattern.
18from charms.reactive import main
19main()
020
=== added file 'deps/layer/layer-basic/hooks/leader-settings-changed'
--- deps/layer/layer-basic/hooks/leader-settings-changed 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-basic/hooks/leader-settings-changed 2017-02-15 08:51:47 +0000
@@ -0,0 +1,19 @@
1#!/usr/bin/env python3
2
3# Load modules from $CHARM_DIR/lib
4import sys
5sys.path.append('lib')
6
7from charms.layer import basic
8basic.bootstrap_charm_deps()
9basic.init_config_states()
10
11
12# This will load and run the appropriate @hook and other decorated
13# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
14# and $CHARM_DIR/hooks/relations.
15#
16# See https://jujucharms.com/docs/stable/authors-charm-building
17# for more information on this pattern.
18from charms.reactive import main
19main()
020
=== added file 'deps/layer/layer-basic/hooks/start'
--- deps/layer/layer-basic/hooks/start 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-basic/hooks/start 2017-02-15 08:51:47 +0000
@@ -0,0 +1,19 @@
1#!/usr/bin/env python3
2
3# Load modules from $CHARM_DIR/lib
4import sys
5sys.path.append('lib')
6
7from charms.layer import basic
8basic.bootstrap_charm_deps()
9basic.init_config_states()
10
11
12# This will load and run the appropriate @hook and other decorated
13# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
14# and $CHARM_DIR/hooks/relations.
15#
16# See https://jujucharms.com/docs/stable/authors-charm-building
17# for more information on this pattern.
18from charms.reactive import main
19main()
020
=== added file 'deps/layer/layer-basic/hooks/stop'
--- deps/layer/layer-basic/hooks/stop 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-basic/hooks/stop 2017-02-15 08:51:47 +0000
@@ -0,0 +1,19 @@
1#!/usr/bin/env python3
2
3# Load modules from $CHARM_DIR/lib
4import sys
5sys.path.append('lib')
6
7from charms.layer import basic
8basic.bootstrap_charm_deps()
9basic.init_config_states()
10
11
12# This will load and run the appropriate @hook and other decorated
13# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
14# and $CHARM_DIR/hooks/relations.
15#
16# See https://jujucharms.com/docs/stable/authors-charm-building
17# for more information on this pattern.
18from charms.reactive import main
19main()
020
=== added file 'deps/layer/layer-basic/hooks/update-status'
--- deps/layer/layer-basic/hooks/update-status 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-basic/hooks/update-status 2017-02-15 08:51:47 +0000
@@ -0,0 +1,19 @@
1#!/usr/bin/env python3
2
3# Load modules from $CHARM_DIR/lib
4import sys
5sys.path.append('lib')
6
7from charms.layer import basic
8basic.bootstrap_charm_deps()
9basic.init_config_states()
10
11
12# This will load and run the appropriate @hook and other decorated
13# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
14# and $CHARM_DIR/hooks/relations.
15#
16# See https://jujucharms.com/docs/stable/authors-charm-building
17# for more information on this pattern.
18from charms.reactive import main
19main()
020
=== added file 'deps/layer/layer-basic/hooks/upgrade-charm'
--- deps/layer/layer-basic/hooks/upgrade-charm 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-basic/hooks/upgrade-charm 2017-02-15 08:51:47 +0000
@@ -0,0 +1,28 @@
1#!/usr/bin/env python3
2
3# Load modules from $CHARM_DIR/lib
4import os
5import sys
6sys.path.append('lib')
7
8# This is an upgrade-charm context, make sure we install latest deps
9if not os.path.exists('wheelhouse/.upgrade'):
10 open('wheelhouse/.upgrade', 'w').close()
11 if os.path.exists('wheelhouse/.bootstrapped'):
12 os.unlink('wheelhouse/.bootstrapped')
13else:
14 os.unlink('wheelhouse/.upgrade')
15
16from charms.layer import basic
17basic.bootstrap_charm_deps()
18basic.init_config_states()
19
20
21# This will load and run the appropriate @hook and other decorated
22# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
23# and $CHARM_DIR/hooks/relations.
24#
25# See https://jujucharms.com/docs/stable/authors-charm-building
26# for more information on this pattern.
27from charms.reactive import main
28main()
029
=== added file 'deps/layer/layer-basic/layer.yaml'
--- deps/layer/layer-basic/layer.yaml 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-basic/layer.yaml 2017-02-15 08:51:47 +0000
@@ -0,0 +1,18 @@
1defines:
2 packages:
3 type: array
4 default: []
5 description: Additional packages to be installed at time of bootstrap
6 use_venv:
7 type: boolean
8 default: false
9 description: >
10 Install charm dependencies (wheelhouse) into a Python virtual environment
11 to help avoid conflicts with other charms or libraries on the machine.
12 include_system_packages:
13 type: boolean
14 default: false
15 description: >
16 If using a virtual environment, allow the venv to see Python packages
17 installed at the system level. This reduces isolation, but is necessary
18 to use Python packages installed via apt-get.
019
=== added directory 'deps/layer/layer-basic/lib'
=== added directory 'deps/layer/layer-basic/lib/charms'
=== added directory 'deps/layer/layer-basic/lib/charms/layer'
=== added file 'deps/layer/layer-basic/lib/charms/layer/__init__.py'
--- deps/layer/layer-basic/lib/charms/layer/__init__.py 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-basic/lib/charms/layer/__init__.py 2017-02-15 08:51:47 +0000
@@ -0,0 +1,21 @@
1import os
2
3
4class LayerOptions(dict):
5 def __init__(self, layer_file, section=None):
6 import yaml # defer, might not be available until bootstrap
7 with open(layer_file) as f:
8 layer = yaml.safe_load(f.read())
9 opts = layer.get('options', {})
10 if section and section in opts:
11 super(LayerOptions, self).__init__(opts.get(section))
12 else:
13 super(LayerOptions, self).__init__(opts)
14
15
16def options(section=None, layer_file=None):
17 if not layer_file:
18 base_dir = os.environ.get('CHARM_DIR', os.getcwd())
19 layer_file = os.path.join(base_dir, 'layer.yaml')
20
21 return LayerOptions(layer_file, section)
022
=== added file 'deps/layer/layer-basic/lib/charms/layer/basic.py'
--- deps/layer/layer-basic/lib/charms/layer/basic.py 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-basic/lib/charms/layer/basic.py 2017-02-15 08:51:47 +0000
@@ -0,0 +1,159 @@
1import os
2import sys
3import shutil
4import platform
5from glob import glob
6from subprocess import check_call
7
8from charms.layer.execd import execd_preinstall
9
10
11def bootstrap_charm_deps():
12 """
13 Set up the base charm dependencies so that the reactive system can run.
14 """
15 # execd must happen first, before any attempt to install packages or
16 # access the network, because sites use this hook to do bespoke
17 # configuration and install secrets so the rest of this bootstrap
18 # and the charm itself can actually succeed. This call does nothing
19 # unless the operator has created and populated $CHARM_DIR/exec.d.
20 execd_preinstall()
21 # ensure that $CHARM_DIR/bin is on the path, for helper scripts
22 os.environ['PATH'] += ':%s' % os.path.join(os.environ['CHARM_DIR'], 'bin')
23 venv = os.path.abspath('../.venv')
24 vbin = os.path.join(venv, 'bin')
25 vpip = os.path.join(vbin, 'pip')
26 vpy = os.path.join(vbin, 'python')
27 if os.path.exists('wheelhouse/.bootstrapped'):
28 from charms import layer
29 cfg = layer.options('basic')
30 if cfg.get('use_venv') and '.venv' not in sys.executable:
31 # activate the venv
32 os.environ['PATH'] = ':'.join([vbin, os.environ['PATH']])
33 reload_interpreter(vpy)
34 return
35 # bootstrap wheelhouse
36 if os.path.exists('wheelhouse'):
37 with open('/root/.pydistutils.cfg', 'w') as fp:
38 # make sure that easy_install also only uses the wheelhouse
39 # (see https://github.com/pypa/pip/issues/410)
40 charm_dir = os.environ['CHARM_DIR']
41 fp.writelines([
42 "[easy_install]\n",
43 "allow_hosts = ''\n",
44 "find_links = file://{}/wheelhouse/\n".format(charm_dir),
45 ])
46 apt_install(['python3-pip', 'python3-setuptools', 'python3-yaml'])
47 from charms import layer
48 cfg = layer.options('basic')
49 # include packages defined in layer.yaml
50 apt_install(cfg.get('packages', []))
51 # if we're using a venv, set it up
52 if cfg.get('use_venv'):
53 if not os.path.exists(venv):
54 distname, version, series = platform.linux_distribution()
55 if series in ('precise', 'trusty'):
56 apt_install(['python-virtualenv'])
57 else:
58 apt_install(['virtualenv'])
59 cmd = ['virtualenv', '-ppython3', '--never-download', venv]
60 if cfg.get('include_system_packages'):
61 cmd.append('--system-site-packages')
62 check_call(cmd)
63 os.environ['PATH'] = ':'.join([vbin, os.environ['PATH']])
64 pip = vpip
65 else:
66 pip = 'pip3'
67 # save a copy of system pip to prevent `pip3 install -U pip`
68 # from changing it
69 if os.path.exists('/usr/bin/pip'):
70 shutil.copy2('/usr/bin/pip', '/usr/bin/pip.save')
71 # need newer pip, to fix spurious Double Requirement error:
72 # https://github.com/pypa/pip/issues/56
73 check_call([pip, 'install', '-U', '--no-index', '-f', 'wheelhouse',
74 'pip'])
75 # install the rest of the wheelhouse deps
76 check_call([pip, 'install', '-U', '--no-index', '-f', 'wheelhouse'] +
77 glob('wheelhouse/*'))
78 if not cfg.get('use_venv'):
79 # restore system pip to prevent `pip3 install -U pip`
80 # from changing it
81 if os.path.exists('/usr/bin/pip.save'):
82 shutil.copy2('/usr/bin/pip.save', '/usr/bin/pip')
83 os.remove('/usr/bin/pip.save')
84 os.remove('/root/.pydistutils.cfg')
85 # flag us as having already bootstrapped so we don't do it again
86 open('wheelhouse/.bootstrapped', 'w').close()
87 # Ensure that the newly bootstrapped libs are available.
88 # Note: this only seems to be an issue with namespace packages.
89 # Non-namespace-package libs (e.g., charmhelpers) are available
90 # without having to reload the interpreter. :/
91 reload_interpreter(vpy if cfg.get('use_venv') else sys.argv[0])
92
93
94def reload_interpreter(python):
95 """
96 Reload the python interpreter to ensure that all deps are available.
97
98 Newly installed modules in namespace packages sometimes seemt to
99 not be picked up by Python 3.
100 """
101 os.execle(python, python, sys.argv[0], os.environ)
102
103
104def apt_install(packages):
105 """
106 Install apt packages.
107
108 This ensures a consistent set of options that are often missed but
109 should really be set.
110 """
111 if isinstance(packages, (str, bytes)):
112 packages = [packages]
113
114 env = os.environ.copy()
115
116 if 'DEBIAN_FRONTEND' not in env:
117 env['DEBIAN_FRONTEND'] = 'noninteractive'
118
119 cmd = ['apt-get',
120 '--option=Dpkg::Options::=--force-confold',
121 '--assume-yes',
122 'install']
123 check_call(cmd + packages, env=env)
124
125
126def init_config_states():
127 import yaml
128 from charmhelpers.core import hookenv
129 from charms.reactive import set_state
130 from charms.reactive import toggle_state
131 config = hookenv.config()
132 config_defaults = {}
133 config_defs = {}
134 config_yaml = os.path.join(hookenv.charm_dir(), 'config.yaml')
135 if os.path.exists(config_yaml):
136 with open(config_yaml) as fp:
137 config_defs = yaml.safe_load(fp).get('options', {})
138 config_defaults = {key: value.get('default')
139 for key, value in config_defs.items()}
140 for opt in config_defs.keys():
141 if config.changed(opt):
142 set_state('config.changed')
143 set_state('config.changed.{}'.format(opt))
144 toggle_state('config.set.{}'.format(opt), config.get(opt))
145 toggle_state('config.default.{}'.format(opt),
146 config.get(opt) == config_defaults[opt])
147 hookenv.atexit(clear_config_states)
148
149
150def clear_config_states():
151 from charmhelpers.core import hookenv, unitdata
152 from charms.reactive import remove_state
153 config = hookenv.config()
154 remove_state('config.changed')
155 for opt in config.keys():
156 remove_state('config.changed.{}'.format(opt))
157 remove_state('config.set.{}'.format(opt))
158 remove_state('config.default.{}'.format(opt))
159 unitdata.kv().flush()
0160
=== added file 'deps/layer/layer-basic/lib/charms/layer/execd.py'
--- deps/layer/layer-basic/lib/charms/layer/execd.py 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-basic/lib/charms/layer/execd.py 2017-02-15 08:51:47 +0000
@@ -0,0 +1,138 @@
1# Copyright 2014-2016 Canonical Limited.
2#
3# This file is part of layer-basic, the reactive base layer for Juju.
4#
5# charm-helpers is free software: you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License version 3 as
7# published by the Free Software Foundation.
8#
9# charm-helpers is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
16
17# This module may only import from the Python standard library.
18import os
19import sys
20import subprocess
21import time
22
23'''
24execd/preinstall
25
26It is often necessary to configure and reconfigure machines
27after provisioning, but before attempting to run the charm.
28Common examples are specialized network configuration, enabling
29of custom hardware, non-standard disk partitioning and filesystems,
30adding secrets and keys required for using a secured network.
31
32The reactive framework's base layer invokes this mechanism as
33early as possible, before any network access is made or dependencies
34unpacked or non-standard modules imported (including the charms.reactive
35framework itself).
36
37Operators needing to use this functionality may branch a charm and
38create an exec.d directory in it. The exec.d directory in turn contains
39one or more subdirectories, each of which contains an executable called
40charm-pre-install and any other required resources. The charm-pre-install
41executables are run, and if successful, state saved so they will not be
42run again.
43
44 $CHARM_DIR/exec.d/mynamespace/charm-pre-install
45
46An alternative to branching a charm is to compose a new charm that contains
47the exec.d directory, using the original charm as a layer,
48
49A charm author could also abuse this mechanism to modify the charm
50environment in unusual ways, but for most purposes it is saner to use
51charmhelpers.core.hookenv.atstart().
52'''
53
54
55def default_execd_dir():
56 return os.path.join(os.environ['CHARM_DIR'], 'exec.d')
57
58
59def execd_module_paths(execd_dir=None):
60 """Generate a list of full paths to modules within execd_dir."""
61 if not execd_dir:
62 execd_dir = default_execd_dir()
63
64 if not os.path.exists(execd_dir):
65 return
66
67 for subpath in os.listdir(execd_dir):
68 module = os.path.join(execd_dir, subpath)
69 if os.path.isdir(module):
70 yield module
71
72
73def execd_submodule_paths(command, execd_dir=None):
74 """Generate a list of full paths to the specified command within exec_dir.
75 """
76 for module_path in execd_module_paths(execd_dir):
77 path = os.path.join(module_path, command)
78 if os.access(path, os.X_OK) and os.path.isfile(path):
79 yield path
80
81
82def execd_sentinel_path(submodule_path):
83 module_path = os.path.dirname(submodule_path)
84 execd_path = os.path.dirname(module_path)
85 module_name = os.path.basename(module_path)
86 submodule_name = os.path.basename(submodule_path)
87 return os.path.join(execd_path,
88 '.{}_{}.done'.format(module_name, submodule_name))
89
90
91def execd_run(command, execd_dir=None, stop_on_error=True, stderr=None):
92 """Run command for each module within execd_dir which defines it."""
93 if stderr is None:
94 stderr = sys.stdout
95 for submodule_path in execd_submodule_paths(command, execd_dir):
96 # Only run each execd once. We cannot simply run them in the
97 # install hook, as potentially storage hooks are run before that.
98 # We cannot rely on them being idempotent.
99 sentinel = execd_sentinel_path(submodule_path)
100 if os.path.exists(sentinel):
101 continue
102
103 try:
104 subprocess.check_call([submodule_path], stderr=stderr,
105 universal_newlines=True)
106 with open(sentinel, 'w') as f:
107 f.write('{} ran successfully {}\n'.format(submodule_path,
108 time.ctime()))
109 f.write('Removing this file will cause it to be run again\n')
110 except subprocess.CalledProcessError as e:
111 # Logs get the details. We can't use juju-log, as the
112 # output may be substantial and exceed command line
113 # length limits.
114 print("ERROR ({}) running {}".format(e.returncode, e.cmd),
115 file=stderr)
116 print("STDOUT<<EOM", file=stderr)
117 print(e.output, file=stderr)
118 print("EOM", file=stderr)
119
120 # Unit workload status gets a shorter fail message.
121 short_path = os.path.relpath(submodule_path)
122 block_msg = "Error ({}) running {}".format(e.returncode,
123 short_path)
124 try:
125 subprocess.check_call(['status-set', 'blocked', block_msg],
126 universal_newlines=True)
127 if stop_on_error:
128 sys.exit(0) # Leave unit in blocked state.
129 except Exception:
130 pass # We care about the exec.d/* failure, not status-set.
131
132 if stop_on_error:
133 sys.exit(e.returncode or 1) # Error state for pre-1.24 Juju
134
135
136def execd_preinstall(execd_dir=None):
137 """Run charm-pre-install for each module within execd_dir."""
138 execd_run('charm-pre-install', execd_dir=execd_dir)
0139
=== added file 'deps/layer/layer-basic/metadata.yaml'
--- deps/layer/layer-basic/metadata.yaml 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-basic/metadata.yaml 2017-02-15 08:51:47 +0000
@@ -0,0 +1,1 @@
1{}
02
=== added directory 'deps/layer/layer-basic/reactive'
=== added file 'deps/layer/layer-basic/reactive/__init__.py'
=== added file 'deps/layer/layer-basic/requirements.txt'
--- deps/layer/layer-basic/requirements.txt 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-basic/requirements.txt 2017-02-15 08:51:47 +0000
@@ -0,0 +1,2 @@
1flake8
2pytest
03
=== added file 'deps/layer/layer-basic/tox.ini'
--- deps/layer/layer-basic/tox.ini 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-basic/tox.ini 2017-02-15 08:51:47 +0000
@@ -0,0 +1,12 @@
1[tox]
2skipsdist=True
3envlist = py34, py35
4skip_missing_interpreters = True
5
6[testenv]
7commands = py.test -v
8deps =
9 -r{toxinidir}/requirements.txt
10
11[flake8]
12exclude=docs
013
=== added file 'deps/layer/layer-basic/wheelhouse.txt'
--- deps/layer/layer-basic/wheelhouse.txt 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-basic/wheelhouse.txt 2017-02-15 08:51:47 +0000
@@ -0,0 +1,3 @@
1pip>=7.0.0,<8.2.0
2charmhelpers>=0.4.0,<1.0.0
3charms.reactive>=0.1.0,<2.0.0
04
=== added directory 'deps/layer/layer-leadership'
=== added file 'deps/layer/layer-leadership/.gitignore'
--- deps/layer/layer-leadership/.gitignore 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-leadership/.gitignore 2017-02-15 08:51:47 +0000
@@ -0,0 +1,2 @@
1*~
2*.pyc
03
=== added file 'deps/layer/layer-leadership/README.md'
--- deps/layer/layer-leadership/README.md 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-leadership/README.md 2017-02-15 08:51:47 +0000
@@ -0,0 +1,88 @@
1# Leadership Layer for Juju Charms
2
3The Leadership layer is for charm-tools and 'charm build', making it
4easier for layered charms to deal with Juju leadership.
5
6This layer will initialize charms.reactive states, allowing you to
7write handlers that will be activated by these states. It allows you
8to completely avoid writing leader-elected and leader-settings-changed
9hooks. As a simple example, these two handlers are all that is required
10to make the leader unit generate a password if it is not already set,
11and have the shared password stored in a file on all units:
12
13```python
14import charms.leadership
15from charmhelpers.core.host import pwgen
16
17
18@when('leadership.is_leader')
19@when_not('leadership.set.admin_password')
20def generate_secret():
21 charms.leadership.leader_set(admin_password=pwgen())
22
23
24@when('leadership.changed.admin_password')
25def store_secret():
26 write_file('/etc/foopass', leader_get('admin_password'))
27```
28
29
30## States
31
32The following states are set appropriately on startup, before any @hook
33decorated methods are invoked:
34
35* `leadership.is_leader`
36
37 This state is set when the unit is the leader. The unit will remain
38 the leader for the remainder of the hook, but may not be leader in
39 future hooks.
40
41* `leadership.set.{varname}`
42
43 This state is set for each leadership setting (ie. the
44 `leadership.set.foo` state will be set if the leader has set
45 the foo leadership setting to any value). It will remain
46 set for the remainder of the hook, unless the unit is the leader
47 and calls `reactive.leadership.leader_set()` and resets the value
48 to None.
49
50* `leadership.changed.{varname}`
51
52 This state is set for each leadership setting that has changed
53 since the last hook. It will remain set for the remainder of the
54 hook. It will not be set in the next hook, unless the leader has
55 changed the leadership setting yet again.
56
57* `leadership.changed`
58
59 One or more leadership settings has changed since the last hook.
60 This state will remain set for the remainder of the hook. It will
61 not be set in the next hook, unless the leader has made further
62 changes.
63
64
65## Methods
66
67The `charms.leadership` module exposes the `leader_set()` and
68`leader_get()` methods, which match the methods found in the
69`charmhelpers.core.hookenv` module. `reactive.leadership.leader_set()`
70should be used instead of the charmhelpers function to ensure that
71the reactive state is updated when the leadership settings are. If you
72do not do this, then you risk handlers waiting on these states to not
73be run on the leader (because when the leader changes settings, it
74triggers leader-settings-changed hooks on the follower units but
75no hooks on itself).
76
77
78## Support
79
80This layer is maintained on Launchpad by
81Stuart Bishop (stuart.bishop@canonical.com).
82
83Code is available using git at git+ssh://git.launchpad.net/layer-leadership.
84
85Bug reports can be made at https://bugs.launchpad.net/layer-leadership.
86
87Queries and comments can be made on the Juju mailing list, Juju IRC
88channels, or at https://answers.launchpad.net/layer-leadership.
089
=== added file 'deps/layer/layer-leadership/copyright'
--- deps/layer/layer-leadership/copyright 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-leadership/copyright 2017-02-15 08:51:47 +0000
@@ -0,0 +1,15 @@
1Copyright 2015-2016 Canonical Ltd.
2
3This file is part of the Leadership Layer for Juju.
4
5This program is free software: you can redistribute it and/or modify
6it under the terms of the GNU General Public License version 3, as
7published by the Free Software Foundation.
8
9This program is distributed in the hope that it will be useful, but
10WITHOUT ANY WARRANTY; without even the implied warranties of
11MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12PURPOSE. See the GNU General Public License for more details.
13
14You should have received a copy of the GNU General Public License
15along with this program. If not, see <http://www.gnu.org/licenses/>.
016
=== added file 'deps/layer/layer-leadership/layer.yaml'
--- deps/layer/layer-leadership/layer.yaml 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-leadership/layer.yaml 2017-02-15 08:51:47 +0000
@@ -0,0 +1,17 @@
1# Copyright 2015-2016 Canonical Ltd.
2#
3# This file is part of the Leadership Layer for Juju.
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 3, as
7# published by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16includes:
17 - layer:basic
018
=== added directory 'deps/layer/layer-leadership/lib'
=== added directory 'deps/layer/layer-leadership/lib/charms'
=== added file 'deps/layer/layer-leadership/lib/charms/__init__.py'
--- deps/layer/layer-leadership/lib/charms/__init__.py 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-leadership/lib/charms/__init__.py 2017-02-15 08:51:47 +0000
@@ -0,0 +1,2 @@
1from pkgutil import extend_path
2__path__ = extend_path(__path__, __name__)
03
=== added file 'deps/layer/layer-leadership/lib/charms/leadership.py'
--- deps/layer/layer-leadership/lib/charms/leadership.py 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-leadership/lib/charms/leadership.py 2017-02-15 08:51:47 +0000
@@ -0,0 +1,58 @@
1# Copyright 2015-2016 Canonical Ltd.
2#
3# This file is part of the Leadership Layer for Juju.
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 3, as
7# published by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17from charmhelpers.core import hookenv
18from charmhelpers.core import unitdata
19
20from charms import reactive
21from charms.reactive import not_unless
22
23
24__all__ = ['leader_get', 'leader_set']
25
26
27@not_unless('leadership.is_leader')
28def leader_set(settings=None, **kw):
29 '''Change leadership settings, per charmhelpers.core.hookenv.leader_set.
30
31 The leadership.set.{key} reactive state will be set while the
32 leadership hook environment setting remains set.
33
34 Changed leadership settings will set the leadership.changed.{key}
35 and leadership.changed states. These states will remain set until
36 the following hook.
37
38 These state changes take effect immediately on the leader, and
39 in future hooks run on non-leaders. In this way both leaders and
40 non-leaders can share handlers, waiting on these states.
41 '''
42 settings = settings or {}
43 settings.update(kw)
44 previous = unitdata.kv().getrange('leadership.settings.', strip=True)
45
46 for key, value in settings.items():
47 if value != previous.get(key):
48 reactive.set_state('leadership.changed.{}'.format(key))
49 reactive.set_state('leadership.changed')
50 reactive.helpers.toggle_state('leadership.set.{}'.format(key),
51 value is not None)
52 hookenv.leader_set(settings)
53 unitdata.kv().update(settings, prefix='leadership.settings.')
54
55
56def leader_get(attribute=None):
57 '''Return leadership settings, per charmhelpers.core.hookenv.leader_get.'''
58 return hookenv.leader_get(attribute)
059
=== added directory 'deps/layer/layer-leadership/reactive'
=== added file 'deps/layer/layer-leadership/reactive/__init__.py'
=== added file 'deps/layer/layer-leadership/reactive/leadership.py'
--- deps/layer/layer-leadership/reactive/leadership.py 1970-01-01 00:00:00 +0000
+++ deps/layer/layer-leadership/reactive/leadership.py 2017-02-15 08:51:47 +0000
@@ -0,0 +1,68 @@
1# Copyright 2015-2016 Canonical Ltd.
2#
3# This file is part of the Leadership Layer for Juju.
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 3, as
7# published by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17from charmhelpers.core import hookenv
18from charmhelpers.core import unitdata
19
20from charms import reactive
21from charms.leadership import leader_get, leader_set
22
23
24__all__ = ['leader_get', 'leader_set'] # Backwards compatibility
25
26
27def initialize_leadership_state():
28 '''Initialize leadership.* states from the hook environment.
29
30 Invoked by hookenv.atstart() so states are available in
31 @hook decorated handlers.
32 '''
33 is_leader = hookenv.is_leader()
34 if is_leader:
35 hookenv.log('Initializing Leadership Layer (is leader)')
36 else:
37 hookenv.log('Initializing Leadership Layer (is follower)')
38
39 reactive.helpers.toggle_state('leadership.is_leader', is_leader)
40
41 previous = unitdata.kv().getrange('leadership.settings.', strip=True)
42 current = hookenv.leader_get()
43
44 # Handle deletions.
45 for key in set(previous.keys()) - set(current.keys()):
46 current[key] = None
47
48 any_changed = False
49 for key, value in current.items():
50 reactive.helpers.toggle_state('leadership.changed.{}'.format(key),
51 value != previous.get(key))
52 if value != previous.get(key):
53 any_changed = True
54 reactive.helpers.toggle_state('leadership.set.{}'.format(key),
55 value is not None)
56 reactive.helpers.toggle_state('leadership.changed', any_changed)
57
58 unitdata.kv().update(current, prefix='leadership.settings.')
59
60
61# Per https://github.com/juju-solutions/charms.reactive/issues/33,
62# this module may be imported multiple times so ensure the
63# initialization hook is only registered once. I have to piggy back
64# onto the namespace of a module imported before reactive discovery
65# to do this.
66if not hasattr(reactive, '_leadership_registered'):
67 hookenv.atstart(initialize_leadership_state)
68 reactive._leadership_registered = True
069
=== added directory 'deps/layer/trunk'
=== added file 'deps/layer/trunk/README.md'
--- deps/layer/trunk/README.md 1970-01-01 00:00:00 +0000
+++ deps/layer/trunk/README.md 2017-02-15 08:51:47 +0000
@@ -0,0 +1,57 @@
1# IBM base layer
2
3The IBM base layer is a common starting point for IBM related charms. Use this
4layer if you are building a layered charm with IBM software.
5
6## Usage
7
8This charm is a base layer and not meant to be built as a charm on its own.
9
10To use this layer, simply include the following in the runtime charm's
11layer.yaml file
12
13```yaml
14includes: ['layer:ibm-base']
15```
16
17## Configuration
18
19**curl_url** - Location of the IBM product installation file(s). This should be a
20URL that curl can use to download files. Multiple URLs should be separated
21by a space.
22
23**NOTE**: cryptographic verification is required and must be
24specified as part of the URL query string with the key a valid hash
25algorithms md5, sha256, or sha512, and the the checksum value itself
26(`http://<url>?[md5|sha256|sha512]=<checksum>`).
27
28For example:
29
30* `http://example.com/file.tgz?sha256=<sum>`
31* `"sftp://example.com/file1.tgz?md5=<sum> ftp://example.com/file2.tgz?md5=<sum>"`
32
33**curl_options** - The options passed to the 'curl' command when fetching files
34from curl_url.
35
36For example:
37
38* `"-u <user:password>"`
39
40**license_accepted** - Some IBM charms require acceptance of a license before
41installation can proceed. If required, setting this option to True indicates
42that you have read and accepted the IBM terms and conditions found in the
43license file referenced by the charm.
44
45## States
46
47**ibm-base.curl.resource.fetched** - When this state is set the IBM base layer has
48downloaded and verified the resources configured by curl_url. If the charm
49does not require any curl resources this state can be ignored.
50
51**ibm-base.license.accepted** - When this state is set the user has signified
52their acceptance of the license found in the charm. If the charm implements
53the Juju 'terms' feature this state can be safely ignored.
54
55## Contacts
56
57IBM Juju Support Team <jujusupp@us.ibm.com>
058
=== added file 'deps/layer/trunk/config.yaml'
--- deps/layer/trunk/config.yaml 1970-01-01 00:00:00 +0000
+++ deps/layer/trunk/config.yaml 2017-02-15 08:51:47 +0000
@@ -0,0 +1,29 @@
1options:
2 curl_url:
3 type: string
4 default: ''
5 description: |
6 Location of the IBM product installation file(s). This should be a URL
7 that curl can use to download files. Multiple URLs should be separated
8 by a space. NOTE: cryptographic verification is required and must be
9 specified as part of the URL query string with the key a valid hash
10 algorithms md5, sha256, or sha512, and the the checksum value itself
11 (http://<url>?[md5|sha256|sha512]=<checksum>).
12 For example:
13 'http://example.com/file.tgz?sha256=<sum>'
14 'sftp://example.com/file1.tgz?md5=<sum> ftp://example.com/file2.tgz?md5=<sum>'
15 curl_opts:
16 type: string
17 default: ''
18 description: |
19 The options passed to the 'curl' command when fetching files from
20 curl_url. For example:
21 '-u <user:password>'
22 license_accepted:
23 type: boolean
24 default: False
25 description: |
26 Some IBM charms require acceptance of a license before installation
27 can proceed. If required, setting this option to True indicates that you
28 have read and accepted the IBM terms and conditions found in the license
29 file referenced by the charm.
030
=== added file 'deps/layer/trunk/layer.yaml'
--- deps/layer/trunk/layer.yaml 1970-01-01 00:00:00 +0000
+++ deps/layer/trunk/layer.yaml 2017-02-15 08:51:47 +0000
@@ -0,0 +1,9 @@
1includes:
2 - 'layer:basic'
3 - 'layer:apt'
4 - 'layer:leadership'
5options:
6 basic:
7 # Setting options for the basic layer.
8 packages:
9 - curl
010
=== added file 'deps/layer/trunk/metadata.yaml'
--- deps/layer/trunk/metadata.yaml 1970-01-01 00:00:00 +0000
+++ deps/layer/trunk/metadata.yaml 2017-02-15 08:51:47 +0000
@@ -0,0 +1,15 @@
1name: ibm-base
2summary: This layer provides a common base for IBM charms to build off of.
3maintainer: IBM Juju Support Team <jujusupp@us.ibm.com>
4description: |
5 This layer provides license acceptance using the terms feature, it also
6 provides apt support from the apt layer, and Juju leadership from the
7 leadership layer.
8min-juju-version: '2.0-beta1'
9series:
10 - trusty
11 - xenial
12tags:
13 - ibm
14 - apt
15 - leadership
016
=== added directory 'deps/layer/trunk/reactive'
=== added file 'deps/layer/trunk/reactive/ibm-base.sh'
--- deps/layer/trunk/reactive/ibm-base.sh 1970-01-01 00:00:00 +0000
+++ deps/layer/trunk/reactive/ibm-base.sh 2017-02-15 08:51:47 +0000
@@ -0,0 +1,107 @@
1#!/bin/bash
2source charms.reactive.sh
3set -e
4
5
6# Utility function to verify a downloaded resource
7# :param: file name
8# :param: checksum type
9# :param: checksum value
10function verify_curl_resource() {
11 local FILE=$1
12 local TYPE=$2
13 local EXPECTED_SUM=$3
14 local CALCULATED_SUM=""
15 local PROG=""
16
17 if [ ! -r ${FILE} ]; then
18 status-set blocked "ibm-base: could not read ${FILE}"
19 juju-log "Could not verify the downloaded resource. File could not be read: ${FILE}"
20 fi
21
22 # Set our checksum utility based on the requested type
23 case "${TYPE}" in
24 md5)
25 PROG='md5sum'
26 ;;
27 sha256)
28 PROG='sha256sum'
29 ;;
30 sha512)
31 PROG='sha512sum'
32 ;;
33 *)
34 status-set blocked "ibm-base: checksum type must be md5, sha215, or sha512"
35 juju-log "Could not verify the downloaded resource ${FILE}. Unknown checksum type: ${TYPE}"
36 return 1
37 esac
38
39 CALCULATED_SUM=`${PROG} ${FILE} | awk '{print $1}'`
40 if [ "${CALCULATED_SUM}" = "${EXPECTED_SUM}" ]; then
41 juju-log "Checksum verified for ${FILE}."
42 return 0
43 else
44 status-set blocked "ibm-base: checksums did not match"
45 juju-log "Checksum mismatch for ${FILE}. Expected ${EXPECTED_SUM}, got ${CALCULATED_SUM}"
46 return 1
47 fi
48}
49
50
51# Fetch curl resources if a URL is configured
52@when 'config.set.curl_url'
53@when_any 'config.new.curl_url' 'config.changed.curl_url' 'config.new.curl_opts' 'config.changed.curl_opts'
54function fetch_curl_resource() {
55 local ARCHIVE_DIR="${CHARM_DIR}/files/archives"
56 local CURL_URL=$(config-get 'curl_url')
57 local CURL_OPTS=$(config-get 'curl_opts')
58
59 status-set maintenance "ibm-base: fetching resource(s)"
60
61 mkdir -p ${ARCHIVE_DIR}
62 cd ${ARCHIVE_DIR}
63 # Multiple URLs may be separated by a space, so loop.
64 for URL_STRING in ${CURL_URL}
65 do
66 # For each URL_STRING, set the url, checksum type, and checksum value.
67 local URL=${URL_STRING%%\?*} # string before the first '?'
68 local FILE_NAME=${URL##*\/} # string after the last '/'
69 local SUM_STRING=${URL_STRING#*\?} # string after the first '?'
70 local SUM_TYPE=${SUM_STRING%%\=*} # string before the first '='
71 local SUM_VALUE=${SUM_STRING#*\=} # string after the first '='
72
73 if [ -z ${FILE_NAME} ]; then
74 FILE_NAME="juju-${RANDOM}"
75 fi
76 curl --silent --show-error ${CURL_OPTS} -o ${FILE_NAME} ${URL}
77
78 # Verify our resource checksum. If this fails, let verify_resource log
79 # the reason and exit successfully. Exiting non-zero would fail the hook,
80 # so return 0 and simply inform the user that verification failed.
81 verify_curl_resource ${FILE_NAME} ${SUM_TYPE} ${SUM_VALUE} || return 0
82 done
83 cd -
84
85 set_state 'ibm-base.curl.resource.fetched'
86 status-set active "ibm-base: curl resource(s) fetched"
87 juju-log 'Curl resource fetched'
88}
89
90
91# Handle license acceptance
92@when 'config.changed.license_accepted'
93function check_license_acceptance() {
94 local LIC_ACCEPTED=$(config-get 'license_accepted')
95
96 # compare lowercase LIC_ACCEPTED (requires bash > 4)
97 if [ "${LIC_ACCEPTED,,}" = "true" ]; then
98 set_state 'ibm-base.license.accepted'
99 juju-log 'License accepted'
100 else
101 remove_state 'ibm-base.license.accepted'
102 juju-log 'License NOT accepted'
103 fi
104}
105
106# Main reactive entry point
107reactive_handler_main
0108
=== added directory 'hooks'
=== removed directory 'hooks'
=== added file 'hooks/config-changed'
--- hooks/config-changed 1970-01-01 00:00:00 +0000
+++ hooks/config-changed 2017-02-15 08:51:47 +0000
@@ -0,0 +1,19 @@
1#!/usr/bin/env python3
2
3# Load modules from $CHARM_DIR/lib
4import sys
5sys.path.append('lib')
6
7from charms.layer import basic
8basic.bootstrap_charm_deps()
9basic.init_config_states()
10
11
12# This will load and run the appropriate @hook and other decorated
13# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
14# and $CHARM_DIR/hooks/relations.
15#
16# See https://jujucharms.com/docs/stable/authors-charm-building
17# for more information on this pattern.
18from charms.reactive import main
19main()
020
=== removed file 'hooks/config-changed'
--- hooks/config-changed 2015-06-10 10:01:41 +0000
+++ hooks/config-changed 1970-01-01 00:00:00 +0000
@@ -1,242 +0,0 @@
1#!/bin/bash
2
3set -e
4MQ_INSTALL_PATH=/opt/mqm
5ARCHITECTURE=`uname -m`
6RPM_INSTALL_ARG="-ivh --nodeps --force-debian"
7if [ "$ARCHITECTURE" = "ppc64le" ]; then
8 RPM_INSTALL_ARG="-ivh --nodeps --force-debian --ignorearch"
9fi
10
11
12# Check whether MQ is installed
13is_mq_installed()
14{
15 if [ -d $MQ_INSTALL_PATH/bin ]; then
16 source $MQ_INSTALL_PATH/bin/setmqenv -s
17 if [ $? == 0 ]; then
18 source $MQ_INSTALL_PATH/bin/setmqenv -s
19 echo "True"
20 fi
21 else
22 echo "False"
23 fi
24}
25
26# Remove MQ, if installed
27remove_software()
28{
29
30 mq_installed=`is_mq_installed`
31 if [ $mq_installed == True ]; then
32 juju-log "Removing IBM MQ software."
33
34 # Stop all queues
35 questr="QMNAME("
36 for queue in `$MQ_INSTALL_PATH/bin/dspmq -o installation | cut -d' ' -f1 `;
37 do
38 # Get the queue manager name
39 queue_mgr_name=${queue:${#questr}:${#queue}-${#questr}-1}
40 juju-log "Stopping queue manager $queue_mgr_name"
41 set +e
42 su -l mqm -c "$MQ_INSTALL_PATH/bin/endmqm -w $queue_mgr_name"
43 su -l mqm -c "$MQ_INSTALL_PATH/bin/endmqlsr -w -m $queue_mgr_name"
44 set -e
45 done
46 # Get list of packages and remove them
47 mq_rpms="`rpm -qa | grep MQSeries`"
48 juju-log "Removing package(s) $mq_rpms"
49 rpm -ev --force-debian $mq_rpms
50 fi
51
52}
53
54# Remove MQ if license not accpeted
55remove_unaccepted_software()
56{
57
58 if [ $1 == False ]; then
59 juju-log "Removing IBM MQ (if installed) as the license agreement is not accepted."
60 remove_software
61 fi
62
63}
64
65# Update system configuration after installing MQ
66configure_system()
67{
68 juju-log "IBM MQ: Updating system configuration."
69
70 # Some containers do not allow system updates.
71 # Prevent the script from failing in such cases
72 set +e
73 shmmax=`sysctl kernel.shmmax | cut -d'=' -f2`
74 if [ $shmmax -lt 268435456 ];
75 then
76 sysctl -w kernel.shmmax=268435456
77 fi
78
79 fmax=`sysctl fs.file-max | cut -d'=' -f2`
80 if [ $fmax -lt 524288 ];
81 then
82 sysctl -w fs.file-max=524288
83 fi
84 sysctl -p
85
86 # Add user ubuntu to mqm group
87 adduser ubuntu mqm
88
89 # Update mqm file limits
90 if [ "`grep mqm /etc/security/limits.conf`"=="" ]; then
91 sed -i 's/# End of file/mqm hard nofile 10240\nmqm soft nofile 10240\n# End of file/' /etc/security/limits.conf
92 fi
93 set -e
94 juju-log "IBM MQ: Completed system configuration update."
95
96}
97
98# Verify installation
99verify_install()
100{
101
102 juju-log "IBM MQ: Verifying installation."
103
104 # Prevent the script from failing on failure.
105 # It could because configure_system call failed
106 set +e
107
108 # Run as mqm user as root will not work
109
110 # Create queue manager
111 juju-log "IBM MQ: Create queue manager QMA."
112 su -l mqm -c "$MQ_INSTALL_PATH/bin/crtmqm QMA"
113 if [ $? == 0 ]; then
114 juju-log "IBM MQ: queue manager QMA created."
115 else
116 juju-log "IBM MQ: Failed to create queue manager QMA."
117 exit 0
118 fi
119
120 # Start queue manager
121 juju-log "IBM MQ: Starting queue manager QMA."
122 su -l mqm -c "$MQ_INSTALL_PATH/bin/strmqm QMA"
123 if [ $? == 0 ]; then
124 juju-log "IBM MQ: Queue manager QMA started."
125 else
126 juju-log "IBM MQ: Failed to start queue manager QMA."
127 exit 0
128 fi
129
130 # Create queue
131 juju-log "IBM MQ: Creating queue."
132 su -l mqm -c "$MQ_INSTALL_PATH/bin/runmqsc QMA < $CHARM_DIR/files/archives/mq_create_queue.mqsc"
133 if [ $? == 0 ]; then
134 juju-log "IBM MQ: Queue created."
135 else
136 juju-log "IBM MQ: Failed to create queue."
137 exit 0
138 fi
139
140 # Send message to queue
141 juju-log "IBM MQ: Sending message to queue."
142 su -l mqm -c "$MQ_INSTALL_PATH/samp/bin/amqsput QUEUE1 QMA < $CHARM_DIR/files/archives/qinput"
143 if [ $? == 0 ]; then
144 juju-log "IBM MQ: Message sent to queue."
145 else
146 juju-log "IBM MQ: Failed to send message to queue."
147 exit 0
148 fi
149
150 # Give MQ some time
151 sleep 5
152
153 # Receive message from queue
154 juju-log "IBM MQ: Receiving message from queue."
155 su -l mqm -c "$MQ_INSTALL_PATH/samp/bin/amqsget QUEUE1 QMA"
156 if [ $? == 0 ]; then
157 juju-log "IBM MQ: Message received from queue."
158 else
159 juju-log "IBM MQ: Failed to receive message from queue."
160 exit 0
161 fi
162
163 # Clean up
164 # Delete queue
165 juju-log "IBM MQ: Deleting queue."
166 su -l mqm -c "$MQ_INSTALL_PATH/bin/runmqsc QMA < $CHARM_DIR/files/archives/mq_delete_queue.mqsc"
167 if [ $? == 0 ]; then
168 juju-log "IBM MQ: Deleted queue."
169 else
170 juju-log "IBM IB: Failed to delete queue."
171 fi
172
173 # End queue manger
174 juju-log "IBM MQ: Stopping queue manager."
175 su -l mqm -c "$MQ_INSTALL_PATH/bin/endmqm QMA"
176 su -l mqm -c "$MQ_INSTALL_PATH/bin/endmqlsr -w -m QMA"
177 if [ $? -eq 0 ]; then
178 juju-log "IBM IB: Queue Manager is stopped."
179 else
180 juju-log "IBM IB: Queue Manager failed to stop."
181 fi
182
183 sleep 10
184
185 juju-log "IBM MQ: Deleting queue manager."
186 su -l mqm -c "$MQ_INSTALL_PATH/bin/dltmqm QMA"
187 if [ $? -eq 0 ]; then
188 juju-log "IBM IB: Queue Manager is deleted."
189 else
190 juju-log "IBM IB: Queue Manager could not be deleted."
191 fi
192
193 set -e
194 juju-log "IBM MQ: Install verification completed."
195
196}
197
198
199juju-log "IBM MQ: Begin config-change hook"
200mq_license_accepted=`config-get accept-ibm-mq-license`
201
202
203# Remove MQ if license not accepted and exit. Else install it
204remove_unaccepted_software $mq_license_accepted
205if [ $mq_license_accepted == False ]; then
206 juju-log "IBM MQ License not accepted."
207
208elif [ $mq_license_accepted == True ]; then
209
210 juju-log "License accepted"
211 mq_inst=`is_mq_installed`
212 if [ -f $CHARM_DIR/files/archives/*.gz ]; then
213 juju-log "IBM MQ: Extracting IBM MQ package."
214 tar xvfz $CHARM_DIR/files/archives/*.gz --strip-components=1 -C $CHARM_DIR/files/archives
215 rm -rf $CHARM_DIR/files/archives/*.gz
216 juju-log "IBM MQ: Extracted IBM MQ package."
217 juju-log "Awaiting acceptance of IBM MQ license (see README on how to accept the license)."
218 fi
219 if [ $mq_inst == False ]; then
220 # Check MQ package availability
221 if [ -f $CHARM_DIR/files/archives/MQSeriesServer*rpm ] && [ -f $CHARM_DIR/files/archives/MQSeriesRuntime*rpm ];
222 then
223 echo "MQ Packages available for installation.";
224 $CHARM_DIR/files/archives/mqlicense.sh -accept
225 juju-log "Installing available MQ packages."
226 rpm $RPM_INSTALL_ARG --prefix $MQ_INSTALL_PATH $CHARM_DIR/files/archives/MQSeries*rpm
227 juju-log "Installation of available MQ packages complete."
228 # Configure system values for MQ
229 configure_system
230 #Verify installation
231 verify_install
232 else
233 echo "MQ Packages missing. Please check README file.";
234 echo "Upgrade MQ charm after adding the MQ packages";
235 exit 0
236 fi
237 fi
238else
239 juju-log " Acceptable values for license is 'True' or 'False'"
240
241fi
242juju-log "IBM MQ: End config-change hook"
2430
=== added file 'hooks/hook.template'
--- hooks/hook.template 1970-01-01 00:00:00 +0000
+++ hooks/hook.template 2017-02-15 08:51:47 +0000
@@ -0,0 +1,19 @@
1#!/usr/bin/env python3
2
3# Load modules from $CHARM_DIR/lib
4import sys
5sys.path.append('lib')
6
7from charms.layer import basic
8basic.bootstrap_charm_deps()
9basic.init_config_states()
10
11
12# This will load and run the appropriate @hook and other decorated
13# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
14# and $CHARM_DIR/hooks/relations.
15#
16# See https://jujucharms.com/docs/stable/authors-charm-building
17# for more information on this pattern.
18from charms.reactive import main
19main()
020
=== added file 'hooks/install'
--- hooks/install 1970-01-01 00:00:00 +0000
+++ hooks/install 2017-02-15 08:51:47 +0000
@@ -0,0 +1,19 @@
1#!/usr/bin/env python3
2
3# Load modules from $CHARM_DIR/lib
4import sys
5sys.path.append('lib')
6
7from charms.layer import basic
8basic.bootstrap_charm_deps()
9basic.init_config_states()
10
11
12# This will load and run the appropriate @hook and other decorated
13# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
14# and $CHARM_DIR/hooks/relations.
15#
16# See https://jujucharms.com/docs/stable/authors-charm-building
17# for more information on this pattern.
18from charms.reactive import main
19main()
020
=== removed file 'hooks/install'
--- hooks/install 2015-06-10 10:01:41 +0000
+++ hooks/install 1970-01-01 00:00:00 +0000
@@ -1,30 +0,0 @@
1#!/bin/bash
2set -e
3#Install RPM dependency
4juju-log "IBM MQ: Begin Install."
5
6# Get the architecture from the uname command.
7ARCHITECTURE=`uname -m`
8if [ "$ARCHITECTURE" != "x86_64" -a "$ARCHITECTURE" != "ppc64le" ]; then
9 juju-log "IBM MQ: Unsupported platform. IBM MQ installed with this Charm supports only the x86_64 and POWER LE (ppc64le) platforms."
10 exit 1
11fi
12
13# Install RPM
14juju-log "IBM MQ: Downloading and installng RPM."
15apt-get install -y rpm
16juju-log "IBM MQ: RPM downloaded and installed."
17
18# Extract IBM MQ archive file, if it exists to files/archives
19if [ -f $CHARM_DIR/files/archives/*.gz ]; then
20 juju-log "IBM MQ: Extracting IBM MQ package."
21 tar xvfz $CHARM_DIR/files/archives/*.gz --strip-components=1 -C $CHARM_DIR/files/archives
22 rm -rf $CHARM_DIR/files/archives/*.gz
23 juju-log "IBM MQ: Extracted IBM MQ package."
24 juju-log "Awaiting acceptance of IBM MQ license (see README on how to accept the license)."
25else
26 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."
27fi
28
29juju-log "IBM MQ: End Install."
30
310
=== added file 'hooks/leader-elected'
--- hooks/leader-elected 1970-01-01 00:00:00 +0000
+++ hooks/leader-elected 2017-02-15 08:51:47 +0000
@@ -0,0 +1,19 @@
1#!/usr/bin/env python3
2
3# Load modules from $CHARM_DIR/lib
4import sys
5sys.path.append('lib')
6
7from charms.layer import basic
8basic.bootstrap_charm_deps()
9basic.init_config_states()
10
11
12# This will load and run the appropriate @hook and other decorated
13# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
14# and $CHARM_DIR/hooks/relations.
15#
16# See https://jujucharms.com/docs/stable/authors-charm-building
17# for more information on this pattern.
18from charms.reactive import main
19main()
020
=== added file 'hooks/leader-settings-changed'
--- hooks/leader-settings-changed 1970-01-01 00:00:00 +0000
+++ hooks/leader-settings-changed 2017-02-15 08:51:47 +0000
@@ -0,0 +1,19 @@
1#!/usr/bin/env python3
2
3# Load modules from $CHARM_DIR/lib
4import sys
5sys.path.append('lib')
6
7from charms.layer import basic
8basic.bootstrap_charm_deps()
9basic.init_config_states()
10
11
12# This will load and run the appropriate @hook and other decorated
13# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
14# and $CHARM_DIR/hooks/relations.
15#
16# See https://jujucharms.com/docs/stable/authors-charm-building
17# for more information on this pattern.
18from charms.reactive import main
19main()
020
=== added file 'hooks/messaging-relation-broken'
--- hooks/messaging-relation-broken 1970-01-01 00:00:00 +0000
+++ hooks/messaging-relation-broken 2017-02-15 08:51:47 +0000
@@ -0,0 +1,19 @@
1#!/usr/bin/env python3
2
3# Load modules from $CHARM_DIR/lib
4import sys
5sys.path.append('lib')
6
7from charms.layer import basic
8basic.bootstrap_charm_deps()
9basic.init_config_states()
10
11
12# This will load and run the appropriate @hook and other decorated
13# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
14# and $CHARM_DIR/hooks/relations.
15#
16# See https://jujucharms.com/docs/stable/authors-charm-building
17# for more information on this pattern.
18from charms.reactive import main
19main()
020
=== added file 'hooks/messaging-relation-changed'
--- hooks/messaging-relation-changed 1970-01-01 00:00:00 +0000
+++ hooks/messaging-relation-changed 2017-02-15 08:51:47 +0000
@@ -0,0 +1,19 @@
1#!/usr/bin/env python3
2
3# Load modules from $CHARM_DIR/lib
4import sys
5sys.path.append('lib')
6
7from charms.layer import basic
8basic.bootstrap_charm_deps()
9basic.init_config_states()
10
11
12# This will load and run the appropriate @hook and other decorated
13# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
14# and $CHARM_DIR/hooks/relations.
15#
16# See https://jujucharms.com/docs/stable/authors-charm-building
17# for more information on this pattern.
18from charms.reactive import main
19main()
020
=== added file 'hooks/messaging-relation-departed'
--- hooks/messaging-relation-departed 1970-01-01 00:00:00 +0000
+++ hooks/messaging-relation-departed 2017-02-15 08:51:47 +0000
@@ -0,0 +1,19 @@
1#!/usr/bin/env python3
2
3# Load modules from $CHARM_DIR/lib
4import sys
5sys.path.append('lib')
6
7from charms.layer import basic
8basic.bootstrap_charm_deps()
9basic.init_config_states()
10
11
12# This will load and run the appropriate @hook and other decorated
13# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
14# and $CHARM_DIR/hooks/relations.
15#
16# See https://jujucharms.com/docs/stable/authors-charm-building
17# for more information on this pattern.
18from charms.reactive import main
19main()
020
=== added file 'hooks/messaging-relation-joined'
--- hooks/messaging-relation-joined 1970-01-01 00:00:00 +0000
+++ hooks/messaging-relation-joined 2017-02-15 08:51:47 +0000
@@ -0,0 +1,19 @@
1#!/usr/bin/env python3
2
3# Load modules from $CHARM_DIR/lib
4import sys
5sys.path.append('lib')
6
7from charms.layer import basic
8basic.bootstrap_charm_deps()
9basic.init_config_states()
10
11
12# This will load and run the appropriate @hook and other decorated
13# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
14# and $CHARM_DIR/hooks/relations.
15#
16# See https://jujucharms.com/docs/stable/authors-charm-building
17# for more information on this pattern.
18from charms.reactive import main
19main()
020
=== removed file 'hooks/messaging-relation-joined'
--- hooks/messaging-relation-joined 2015-05-03 16:45:29 +0000
+++ hooks/messaging-relation-joined 1970-01-01 00:00:00 +0000
@@ -1,12 +0,0 @@
1#!/bin/bash
2
3juju-log "IBM MQ: Begin messaging-relation-joined hook."
4set -e
5mq_license_accepted=`config-get accept-ibm-mq-license`
6if [ "$mq_license_accepted" == "False" ]; then
7 juju-log "IBM MQ: IBM MQ License is not accepted."
8 juju-log "Delete the relation. Accept the IBM MQ License, as per the README, before setting up any relation."
9 exit 0
10fi
11relation-set hostname=`unit-get private-address` port=1414
12juju-log "IBM MQ: End messaging-relation-joined hook."
130
=== added directory 'hooks/relations'
=== added directory 'hooks/relations/ibm-mq'
=== added file 'hooks/relations/ibm-mq/README.md'
--- hooks/relations/ibm-mq/README.md 1970-01-01 00:00:00 +0000
+++ hooks/relations/ibm-mq/README.md 2017-02-15 08:51:47 +0000
@@ -0,0 +1,42 @@
1Overview
2-----------
3
4This 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.
5The provider end of this interface provides the Queue Manager name, Queue Name, IP Address and Port.
6The 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`
7
8
9Usage
10------
11
12#### Provides
13IBM MQ product will provide this interface. This interface layer will set the following states, as appropriate:
14
15 - `{relation_name}.connected`: The relation is established, IBM-MQ is ready to send it's information.
16
17 - `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.
18
19 - `set_mq_details()` - Sets Queue Manager name, Queue Name, IP Address and Port for the connected services.
20 As a Queue Name it sets a filename where MQSC Commands can be edited for further operation.
21
22
23 - `{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.
24
25#### Requires
26
27Consumer 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.
28This interface layer will set the following states, as appropriate:
29
30- `{relation_name}.connected` : The consumer charm has been related to a IBM-MQ provider charm.
31 At this point, the charm waits for Provider charm to send details like Queue Manager name, Queue Name, IP Address and Port.
32
33 - `set_hostname()` - sets the `hostname`, so that IBM-MQ can get the consumer/client hostname to allow to send/receive messages.
34
35- `{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.
36
37 - `get_qm_name()` - returns the `QM_Name` that IBM-MQ has created.
38 - `get_qname()` - returns the `QName` that IBM-MQ has created.
39 - `get_mq_hostname()` - returns the `hostname` that IBM-MQ has created.
40 - `get_mq_port()` - returns the `MQ Port` that IBM-MQ has created.
41
42- `{relation_name}.departed` : The relation has been removed. Any cleanup related to the provider charm should happen now.
043
=== added file 'hooks/relations/ibm-mq/__init__.py'
=== added file 'hooks/relations/ibm-mq/interface.yaml'
--- hooks/relations/ibm-mq/interface.yaml 1970-01-01 00:00:00 +0000
+++ hooks/relations/ibm-mq/interface.yaml 2017-02-15 08:51:47 +0000
@@ -0,0 +1,4 @@
1name: ibm-mq
2summary: Interface for relating to ibm-mq
3version: 1
4maintainer: "Juju Support <jujusupp@us.ibm.com>"
0\ No newline at end of file5\ No newline at end of file
16
=== added file 'hooks/relations/ibm-mq/provides.py'
--- hooks/relations/ibm-mq/provides.py 1970-01-01 00:00:00 +0000
+++ hooks/relations/ibm-mq/provides.py 2017-02-15 08:51:47 +0000
@@ -0,0 +1,50 @@
1from charms.reactive import hook
2from charms.reactive import RelationBase
3from charms.reactive import scopes
4
5
6class mqProvides(RelationBase):
7 # Every unit connecting will get the same information
8 scope = scopes.SERVICE
9
10 @hook('{provides:ibm-mq}-relation-joined')
11 def joined(self):
12 conversation = self.conversation()
13 conversation.remove_state('{relation_name}.departed')
14 conversation.set_state('{relation_name}.connected')
15
16 @hook('{provides:ibm-mq}-relation-departed')
17 def departed(self):
18 conversation = self.conversation()
19 conversation.remove_state('{relation_name}.connected')
20 conversation.set_state('{relation_name}.departed')
21
22 def dismiss(self, service):
23 conversation = self.conversation(service)
24 conversation.remove_state('{relation_name}.departed')
25
26 def reset_states(self, service):
27 conversation = self.conversation(service)
28 conversation.remove_state('{relation_name}.connected')
29 conversation.remove_state('{relation_name}.departed')
30
31 def set_mq_details(self, service, QM_Name, Qname, host, port):
32 conversation = self.conversation(service)
33 conversation.set_remote(data={
34 'QM_Name': QM_Name,
35 'Qname': Qname,
36 'host': host,
37 'port': port,
38 })
39
40 def get_consumer_hostname(self):
41 return self.get_remote('host')
42
43 def services(self):
44 """
45 Return a list of services requesting MQ.
46 """
47 service = []
48 for conversation in self.conversations():
49 service.append(conversation.scope)
50 return service
051
=== added file 'hooks/relations/ibm-mq/requires.py'
--- hooks/relations/ibm-mq/requires.py 1970-01-01 00:00:00 +0000
+++ hooks/relations/ibm-mq/requires.py 2017-02-15 08:51:47 +0000
@@ -0,0 +1,42 @@
1from charms.reactive import hook
2from charms.reactive import RelationBase
3from charms.reactive import scopes
4
5
6class mq1Requires(RelationBase):
7 scope = scopes.GLOBAL
8
9 @hook('{requires:ibm-mq}-relation-joined')
10 def joined(self):
11 self.remove_state('{relation_name}.departed')
12 self.set_state('{relation_name}.connected')
13
14 @hook('{requires:ibm-mq}-relation-changed')
15 def changed(self):
16 if str(self.get_remote('port')) != "None":
17 self.set_state('{relation_name}.ready')
18 print("Status is relation_name.ready in requires")
19
20 @hook('{requires:ibm-mq}-relation-departed')
21 def departed(self):
22 self.remove_state('{relation_name}.connected')
23 self.remove_state('{relation_name}.ready')
24 self.set_state('{relation_name}.departed')
25
26 def set_hostname(self, host):
27 conversation = self.conversation()
28 conversation.set_remote(data={
29 'host': host,
30 })
31
32 def get_qm_name(self):
33 return self.get_remote('QM_Name')
34
35 def get_qname(self):
36 return self.get_remote('Qname')
37
38 def get_mq_hostname(self):
39 return self.get_remote('host')
40
41 def get_mq_port(self):
42 return self.get_remote('port')
043
=== added file 'hooks/start'
--- hooks/start 1970-01-01 00:00:00 +0000
+++ hooks/start 2017-02-15 08:51:47 +0000
@@ -0,0 +1,19 @@
1#!/usr/bin/env python3
2
3# Load modules from $CHARM_DIR/lib
4import sys
5sys.path.append('lib')
6
7from charms.layer import basic
8basic.bootstrap_charm_deps()
9basic.init_config_states()
10
11
12# This will load and run the appropriate @hook and other decorated
13# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
14# and $CHARM_DIR/hooks/relations.
15#
16# See https://jujucharms.com/docs/stable/authors-charm-building
17# for more information on this pattern.
18from charms.reactive import main
19main()
020
=== removed file 'hooks/start'
--- hooks/start 2015-03-06 08:32:33 +0000
+++ hooks/start 1970-01-01 00:00:00 +0000
@@ -1,8 +0,0 @@
1#!/bin/bash
2
3# Juju command to open port
4
5set -e
6juju-log "IBM MQ: Begin Start hook"
7open-port 1414/TCP
8juju-log "IBM MQ: End Start hook"
90
=== added file 'hooks/stop'
--- hooks/stop 1970-01-01 00:00:00 +0000
+++ hooks/stop 2017-02-15 08:51:47 +0000
@@ -0,0 +1,19 @@
1#!/usr/bin/env python3
2
3# Load modules from $CHARM_DIR/lib
4import sys
5sys.path.append('lib')
6
7from charms.layer import basic
8basic.bootstrap_charm_deps()
9basic.init_config_states()
10
11
12# This will load and run the appropriate @hook and other decorated
13# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
14# and $CHARM_DIR/hooks/relations.
15#
16# See https://jujucharms.com/docs/stable/authors-charm-building
17# for more information on this pattern.
18from charms.reactive import main
19main()
020
=== removed file 'hooks/stop'
--- hooks/stop 2015-03-06 08:32:33 +0000
+++ hooks/stop 1970-01-01 00:00:00 +0000
@@ -1,6 +0,0 @@
1#!/bin/bash
2
3set -e
4juju-log "IBM MQ: Begin Stop hook"
5close-port 1414/TCP
6juju-log "IBM MQ: End Stop hook"
70
=== added file 'hooks/update-status'
--- hooks/update-status 1970-01-01 00:00:00 +0000
+++ hooks/update-status 2017-02-15 08:51:47 +0000
@@ -0,0 +1,19 @@
1#!/usr/bin/env python3
2
3# Load modules from $CHARM_DIR/lib
4import sys
5sys.path.append('lib')
6
7from charms.layer import basic
8basic.bootstrap_charm_deps()
9basic.init_config_states()
10
11
12# This will load and run the appropriate @hook and other decorated
13# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
14# and $CHARM_DIR/hooks/relations.
15#
16# See https://jujucharms.com/docs/stable/authors-charm-building
17# for more information on this pattern.
18from charms.reactive import main
19main()
020
=== added file 'hooks/upgrade-charm'
--- hooks/upgrade-charm 1970-01-01 00:00:00 +0000
+++ hooks/upgrade-charm 2017-02-15 08:51:47 +0000
@@ -0,0 +1,28 @@
1#!/usr/bin/env python3
2
3# Load modules from $CHARM_DIR/lib
4import os
5import sys
6sys.path.append('lib')
7
8# This is an upgrade-charm context, make sure we install latest deps
9if not os.path.exists('wheelhouse/.upgrade'):
10 open('wheelhouse/.upgrade', 'w').close()
11 if os.path.exists('wheelhouse/.bootstrapped'):
12 os.unlink('wheelhouse/.bootstrapped')
13else:
14 os.unlink('wheelhouse/.upgrade')
15
16from charms.layer import basic
17basic.bootstrap_charm_deps()
18basic.init_config_states()
19
20
21# This will load and run the appropriate @hook and other decorated
22# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive,
23# and $CHARM_DIR/hooks/relations.
24#
25# See https://jujucharms.com/docs/stable/authors-charm-building
26# for more information on this pattern.
27from charms.reactive import main
28main()
029
=== added file 'layer.yaml'
--- layer.yaml 1970-01-01 00:00:00 +0000
+++ layer.yaml 2017-02-15 08:51:47 +0000
@@ -0,0 +1,21 @@
1"options":
2 "basic":
3 "packages":
4 - "curl"
5 - "rpm"
6 "use_venv": !!bool "false"
7 "include_system_packages": !!bool "false"
8 "ibm-base": {}
9 "leadership": {}
10 "ibm-mq": {}
11 "apt":
12 "packages": []
13"includes":
14- "layer:basic"
15- "layer:basic"
16- "layer:apt"
17- "layer:leadership"
18- "layer:ibm-base"
19- "interface:ibm-mq"
20"repo": "bzr+ssh://bazaar.launchpad.net/~ibmcharmers/charms/trusty/layer-ibm-mq/trunk/"
21"is": "ibm-mq"
022
=== added directory 'lib'
=== added directory 'lib/charms'
=== added file 'lib/charms/__init__.py'
--- lib/charms/__init__.py 1970-01-01 00:00:00 +0000
+++ lib/charms/__init__.py 2017-02-15 08:51:47 +0000
@@ -0,0 +1,2 @@
1from pkgutil import extend_path
2__path__ = extend_path(__path__, __name__)
03
=== added file 'lib/charms/apt.py'
--- lib/charms/apt.py 1970-01-01 00:00:00 +0000
+++ lib/charms/apt.py 2017-02-15 08:51:47 +0000
@@ -0,0 +1,182 @@
1# Copyright 2015-2016 Canonical Ltd.
2#
3# This file is part of the Apt layer for Juju.
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 3, as
7# published by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17'''
18charms.reactive helpers for dealing with deb packages.
19
20Add apt package sources using add_source(). Queue deb packages for
21installation with install(). Configure and work with your software
22once the apt.installed.{packagename} state is set.
23'''
24import itertools
25import subprocess
26
27from charmhelpers import fetch
28from charmhelpers.core import hookenv, unitdata
29from charms import reactive
30
31
32__all__ = ['add_source', 'update', 'queue_install', 'install_queued',
33 'installed', 'purge', 'ensure_package_status']
34
35
36def add_source(source, key=None):
37 '''Add an apt source.
38
39 Sets the apt.needs_update state.
40
41 A source may be either a line that can be added directly to
42 sources.list(5), or in the form ppa:<user>/<ppa-name> for adding
43 Personal Package Archives, or a distribution component to enable.
44
45 The package signing key should be an ASCII armoured GPG key. While
46 GPG key ids are also supported, the retrieval mechanism is insecure.
47 There is no need to specify the package signing key for PPAs or for
48 the main Ubuntu archives.
49 '''
50 # Maybe we should remember which sources have been added already
51 # so we don't waste time re-adding them. Is this time significant?
52 fetch.add_source(source, key)
53 reactive.set_state('apt.needs_update')
54
55
56def queue_install(packages, options=None):
57 """Queue one or more deb packages for install.
58
59 The `apt.installed.{name}` state is set once the package is installed.
60
61 If a package has already been installed it will not be reinstalled.
62
63 If a package has already been queued it will not be requeued, and
64 the install options will not be changed.
65
66 Sets the apt.queued_installs state.
67 """
68 if isinstance(packages, str):
69 packages = [packages]
70 # Filter installed packages.
71 store = unitdata.kv()
72 queued_packages = store.getrange('apt.install_queue.', strip=True)
73 packages = {package: options for package in packages
74 if not (package in queued_packages or
75 reactive.helpers.is_state('apt.installed.' + package))}
76 if packages:
77 unitdata.kv().update(packages, prefix='apt.install_queue.')
78 reactive.set_state('apt.queued_installs')
79
80
81def installed():
82 '''Return the set of deb packages completed install'''
83 return set(state.split('.', 2)[2] for state in reactive.bus.get_states()
84 if state.startswith('apt.installed.'))
85
86
87def purge(packages):
88 """Purge one or more deb packages from the system"""
89 fetch.apt_purge(packages, fatal=True)
90 store = unitdata.kv()
91 store.unsetrange(packages, prefix='apt.install_queue.')
92 for package in packages:
93 reactive.remove_state('apt.installed.{}'.format(package))
94
95
96def update():
97 """Update the apt cache.
98
99 Removes the apt.needs_update state.
100 """
101 status_set(None, 'Updating apt cache')
102 fetch.apt_update(fatal=True) # Friends don't let friends set fatal=False
103 reactive.remove_state('apt.needs_update')
104
105
106def install_queued():
107 '''Installs queued deb packages.
108
109 Removes the apt.queued_installs state and sets the apt.installed state.
110
111 On failure, sets the unit's workload state to 'blocked' and returns
112 False. Package installs remain queued.
113
114 On success, sets the apt.installed.{packagename} state for each
115 installed package and returns True.
116 '''
117 store = unitdata.kv()
118 queue = sorted((options, package)
119 for package, options in store.getrange('apt.install_queue.',
120 strip=True).items())
121
122 installed = set()
123 for options, batch in itertools.groupby(queue, lambda x: x[0]):
124 packages = [b[1] for b in batch]
125 try:
126 status_set(None, 'Installing {}'.format(','.join(packages)))
127 fetch.apt_install(packages, options, fatal=True)
128 store.unsetrange(packages, prefix='apt.install_queue.')
129 installed.update(packages)
130 except subprocess.CalledProcessError:
131 status_set('blocked',
132 'Unable to install packages {}'
133 .format(','.join(packages)))
134 return False # Without setting reactive state.
135
136 for package in installed:
137 reactive.set_state('apt.installed.{}'.format(package))
138
139 reactive.remove_state('apt.queued_installs')
140 return True
141
142
143def ensure_package_status():
144 '''Hold or unhold packages per the package_status configuration option.
145
146 All packages installed using this module and handlers are affected.
147
148 An mechanism may be added in the future to override this for a
149 subset of installed packages.
150 '''
151 packages = installed()
152 if not packages:
153 return
154 config = hookenv.config()
155 package_status = config['package_status']
156 changed = reactive.helpers.data_changed('apt.package_status',
157 (package_status, sorted(packages)))
158 if changed:
159 if package_status == 'hold':
160 hookenv.log('Holding packages {}'.format(','.join(packages)))
161 fetch.apt_hold(packages)
162 else:
163 hookenv.log('Unholding packages {}'.format(','.join(packages)))
164 fetch.apt_unhold(packages)
165 reactive.remove_state('apt.needs_hold')
166
167
168def status_set(state, message):
169 """Set the unit's workload status.
170
171 Set state == None to keep the same state and just change the message.
172 """
173 if state is None:
174 state = hookenv.status_get()[0]
175 if state == 'unknown':
176 state = 'maintenance' # Guess
177 if state in ('error', 'blocked'):
178 lvl = hookenv.WARNING
179 else:
180 lvl = hookenv.INFO
181 hookenv.status_set(state, message)
182 hookenv.log('{}: {}'.format(state, message), lvl)
0183
=== added directory 'lib/charms/layer'
=== added file 'lib/charms/layer/__init__.py'
--- lib/charms/layer/__init__.py 1970-01-01 00:00:00 +0000
+++ lib/charms/layer/__init__.py 2017-02-15 08:51:47 +0000
@@ -0,0 +1,21 @@
1import os
2
3
4class LayerOptions(dict):
5 def __init__(self, layer_file, section=None):
6 import yaml # defer, might not be available until bootstrap
7 with open(layer_file) as f:
8 layer = yaml.safe_load(f.read())
9 opts = layer.get('options', {})
10 if section and section in opts:
11 super(LayerOptions, self).__init__(opts.get(section))
12 else:
13 super(LayerOptions, self).__init__(opts)
14
15
16def options(section=None, layer_file=None):
17 if not layer_file:
18 base_dir = os.environ.get('CHARM_DIR', os.getcwd())
19 layer_file = os.path.join(base_dir, 'layer.yaml')
20
21 return LayerOptions(layer_file, section)
022
=== added file 'lib/charms/layer/basic.py'
--- lib/charms/layer/basic.py 1970-01-01 00:00:00 +0000
+++ lib/charms/layer/basic.py 2017-02-15 08:51:47 +0000
@@ -0,0 +1,159 @@
1import os
2import sys
3import shutil
4import platform
5from glob import glob
6from subprocess import check_call
7
8from charms.layer.execd import execd_preinstall
9
10
11def bootstrap_charm_deps():
12 """
13 Set up the base charm dependencies so that the reactive system can run.
14 """
15 # execd must happen first, before any attempt to install packages or
16 # access the network, because sites use this hook to do bespoke
17 # configuration and install secrets so the rest of this bootstrap
18 # and the charm itself can actually succeed. This call does nothing
19 # unless the operator has created and populated $CHARM_DIR/exec.d.
20 execd_preinstall()
21 # ensure that $CHARM_DIR/bin is on the path, for helper scripts
22 os.environ['PATH'] += ':%s' % os.path.join(os.environ['CHARM_DIR'], 'bin')
23 venv = os.path.abspath('../.venv')
24 vbin = os.path.join(venv, 'bin')
25 vpip = os.path.join(vbin, 'pip')
26 vpy = os.path.join(vbin, 'python')
27 if os.path.exists('wheelhouse/.bootstrapped'):
28 from charms import layer
29 cfg = layer.options('basic')
30 if cfg.get('use_venv') and '.venv' not in sys.executable:
31 # activate the venv
32 os.environ['PATH'] = ':'.join([vbin, os.environ['PATH']])
33 reload_interpreter(vpy)
34 return
35 # bootstrap wheelhouse
36 if os.path.exists('wheelhouse'):
37 with open('/root/.pydistutils.cfg', 'w') as fp:
38 # make sure that easy_install also only uses the wheelhouse
39 # (see https://github.com/pypa/pip/issues/410)
40 charm_dir = os.environ['CHARM_DIR']
41 fp.writelines([
42 "[easy_install]\n",
43 "allow_hosts = ''\n",
44 "find_links = file://{}/wheelhouse/\n".format(charm_dir),
45 ])
46 apt_install(['python3-pip', 'python3-setuptools', 'python3-yaml'])
47 from charms import layer
48 cfg = layer.options('basic')
49 # include packages defined in layer.yaml
50 apt_install(cfg.get('packages', []))
51 # if we're using a venv, set it up
52 if cfg.get('use_venv'):
53 if not os.path.exists(venv):
54 distname, version, series = platform.linux_distribution()
55 if series in ('precise', 'trusty'):
56 apt_install(['python-virtualenv'])
57 else:
58 apt_install(['virtualenv'])
59 cmd = ['virtualenv', '-ppython3', '--never-download', venv]
60 if cfg.get('include_system_packages'):
61 cmd.append('--system-site-packages')
62 check_call(cmd)
63 os.environ['PATH'] = ':'.join([vbin, os.environ['PATH']])
64 pip = vpip
65 else:
66 pip = 'pip3'
67 # save a copy of system pip to prevent `pip3 install -U pip`
68 # from changing it
69 if os.path.exists('/usr/bin/pip'):
70 shutil.copy2('/usr/bin/pip', '/usr/bin/pip.save')
71 # need newer pip, to fix spurious Double Requirement error:
72 # https://github.com/pypa/pip/issues/56
73 check_call([pip, 'install', '-U', '--no-index', '-f', 'wheelhouse',
74 'pip'])
75 # install the rest of the wheelhouse deps
76 check_call([pip, 'install', '-U', '--no-index', '-f', 'wheelhouse'] +
77 glob('wheelhouse/*'))
78 if not cfg.get('use_venv'):
79 # restore system pip to prevent `pip3 install -U pip`
80 # from changing it
81 if os.path.exists('/usr/bin/pip.save'):
82 shutil.copy2('/usr/bin/pip.save', '/usr/bin/pip')
83 os.remove('/usr/bin/pip.save')
84 os.remove('/root/.pydistutils.cfg')
85 # flag us as having already bootstrapped so we don't do it again
86 open('wheelhouse/.bootstrapped', 'w').close()
87 # Ensure that the newly bootstrapped libs are available.
88 # Note: this only seems to be an issue with namespace packages.
89 # Non-namespace-package libs (e.g., charmhelpers) are available
90 # without having to reload the interpreter. :/
91 reload_interpreter(vpy if cfg.get('use_venv') else sys.argv[0])
92
93
94def reload_interpreter(python):
95 """
96 Reload the python interpreter to ensure that all deps are available.
97
98 Newly installed modules in namespace packages sometimes seemt to
99 not be picked up by Python 3.
100 """
101 os.execle(python, python, sys.argv[0], os.environ)
102
103
104def apt_install(packages):
105 """
106 Install apt packages.
107
108 This ensures a consistent set of options that are often missed but
109 should really be set.
110 """
111 if isinstance(packages, (str, bytes)):
112 packages = [packages]
113
114 env = os.environ.copy()
115
116 if 'DEBIAN_FRONTEND' not in env:
117 env['DEBIAN_FRONTEND'] = 'noninteractive'
118
119 cmd = ['apt-get',
120 '--option=Dpkg::Options::=--force-confold',
121 '--assume-yes',
122 'install']
123 check_call(cmd + packages, env=env)
124
125
126def init_config_states():
127 import yaml
128 from charmhelpers.core import hookenv
129 from charms.reactive import set_state
130 from charms.reactive import toggle_state
131 config = hookenv.config()
132 config_defaults = {}
133 config_defs = {}
134 config_yaml = os.path.join(hookenv.charm_dir(), 'config.yaml')
135 if os.path.exists(config_yaml):
136 with open(config_yaml) as fp:
137 config_defs = yaml.safe_load(fp).get('options', {})
138 config_defaults = {key: value.get('default')
139 for key, value in config_defs.items()}
140 for opt in config_defs.keys():
141 if config.changed(opt):
142 set_state('config.changed')
143 set_state('config.changed.{}'.format(opt))
144 toggle_state('config.set.{}'.format(opt), config.get(opt))
145 toggle_state('config.default.{}'.format(opt),
146 config.get(opt) == config_defaults[opt])
147 hookenv.atexit(clear_config_states)
148
149
150def clear_config_states():
151 from charmhelpers.core import hookenv, unitdata
152 from charms.reactive import remove_state
153 config = hookenv.config()
154 remove_state('config.changed')
155 for opt in config.keys():
156 remove_state('config.changed.{}'.format(opt))
157 remove_state('config.set.{}'.format(opt))
158 remove_state('config.default.{}'.format(opt))
159 unitdata.kv().flush()
0160
=== added file 'lib/charms/layer/execd.py'
--- lib/charms/layer/execd.py 1970-01-01 00:00:00 +0000
+++ lib/charms/layer/execd.py 2017-02-15 08:51:47 +0000
@@ -0,0 +1,138 @@
1# Copyright 2014-2016 Canonical Limited.
2#
3# This file is part of layer-basic, the reactive base layer for Juju.
4#
5# charm-helpers is free software: you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License version 3 as
7# published by the Free Software Foundation.
8#
9# charm-helpers is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
16
17# This module may only import from the Python standard library.
18import os
19import sys
20import subprocess
21import time
22
23'''
24execd/preinstall
25
26It is often necessary to configure and reconfigure machines
27after provisioning, but before attempting to run the charm.
28Common examples are specialized network configuration, enabling
29of custom hardware, non-standard disk partitioning and filesystems,
30adding secrets and keys required for using a secured network.
31
32The reactive framework's base layer invokes this mechanism as
33early as possible, before any network access is made or dependencies
34unpacked or non-standard modules imported (including the charms.reactive
35framework itself).
36
37Operators needing to use this functionality may branch a charm and
38create an exec.d directory in it. The exec.d directory in turn contains
39one or more subdirectories, each of which contains an executable called
40charm-pre-install and any other required resources. The charm-pre-install
41executables are run, and if successful, state saved so they will not be
42run again.
43
44 $CHARM_DIR/exec.d/mynamespace/charm-pre-install
45
46An alternative to branching a charm is to compose a new charm that contains
47the exec.d directory, using the original charm as a layer,
48
49A charm author could also abuse this mechanism to modify the charm
50environment in unusual ways, but for most purposes it is saner to use
51charmhelpers.core.hookenv.atstart().
52'''
53
54
55def default_execd_dir():
56 return os.path.join(os.environ['CHARM_DIR'], 'exec.d')
57
58
59def execd_module_paths(execd_dir=None):
60 """Generate a list of full paths to modules within execd_dir."""
61 if not execd_dir:
62 execd_dir = default_execd_dir()
63
64 if not os.path.exists(execd_dir):
65 return
66
67 for subpath in os.listdir(execd_dir):
68 module = os.path.join(execd_dir, subpath)
69 if os.path.isdir(module):
70 yield module
71
72
73def execd_submodule_paths(command, execd_dir=None):
74 """Generate a list of full paths to the specified command within exec_dir.
75 """
76 for module_path in execd_module_paths(execd_dir):
77 path = os.path.join(module_path, command)
78 if os.access(path, os.X_OK) and os.path.isfile(path):
79 yield path
80
81
82def execd_sentinel_path(submodule_path):
83 module_path = os.path.dirname(submodule_path)
84 execd_path = os.path.dirname(module_path)
85 module_name = os.path.basename(module_path)
86 submodule_name = os.path.basename(submodule_path)
87 return os.path.join(execd_path,
88 '.{}_{}.done'.format(module_name, submodule_name))
89
90
91def execd_run(command, execd_dir=None, stop_on_error=True, stderr=None):
92 """Run command for each module within execd_dir which defines it."""
93 if stderr is None:
94 stderr = sys.stdout
95 for submodule_path in execd_submodule_paths(command, execd_dir):
96 # Only run each execd once. We cannot simply run them in the
97 # install hook, as potentially storage hooks are run before that.
98 # We cannot rely on them being idempotent.
99 sentinel = execd_sentinel_path(submodule_path)
100 if os.path.exists(sentinel):
101 continue
102
103 try:
104 subprocess.check_call([submodule_path], stderr=stderr,
105 universal_newlines=True)
106 with open(sentinel, 'w') as f:
107 f.write('{} ran successfully {}\n'.format(submodule_path,
108 time.ctime()))
109 f.write('Removing this file will cause it to be run again\n')
110 except subprocess.CalledProcessError as e:
111 # Logs get the details. We can't use juju-log, as the
112 # output may be substantial and exceed command line
113 # length limits.
114 print("ERROR ({}) running {}".format(e.returncode, e.cmd),
115 file=stderr)
116 print("STDOUT<<EOM", file=stderr)
117 print(e.output, file=stderr)
118 print("EOM", file=stderr)
119
120 # Unit workload status gets a shorter fail message.
121 short_path = os.path.relpath(submodule_path)
122 block_msg = "Error ({}) running {}".format(e.returncode,
123 short_path)
124 try:
125 subprocess.check_call(['status-set', 'blocked', block_msg],
126 universal_newlines=True)
127 if stop_on_error:
128 sys.exit(0) # Leave unit in blocked state.
129 except Exception:
130 pass # We care about the exec.d/* failure, not status-set.
131
132 if stop_on_error:
133 sys.exit(e.returncode or 1) # Error state for pre-1.24 Juju
134
135
136def execd_preinstall(execd_dir=None):
137 """Run charm-pre-install for each module within execd_dir."""
138 execd_run('charm-pre-install', execd_dir=execd_dir)
0139
=== added file 'lib/charms/leadership.py'
--- lib/charms/leadership.py 1970-01-01 00:00:00 +0000
+++ lib/charms/leadership.py 2017-02-15 08:51:47 +0000
@@ -0,0 +1,58 @@
1# Copyright 2015-2016 Canonical Ltd.
2#
3# This file is part of the Leadership Layer for Juju.
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 3, as
7# published by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17from charmhelpers.core import hookenv
18from charmhelpers.core import unitdata
19
20from charms import reactive
21from charms.reactive import not_unless
22
23
24__all__ = ['leader_get', 'leader_set']
25
26
27@not_unless('leadership.is_leader')
28def leader_set(settings=None, **kw):
29 '''Change leadership settings, per charmhelpers.core.hookenv.leader_set.
30
31 The leadership.set.{key} reactive state will be set while the
32 leadership hook environment setting remains set.
33
34 Changed leadership settings will set the leadership.changed.{key}
35 and leadership.changed states. These states will remain set until
36 the following hook.
37
38 These state changes take effect immediately on the leader, and
39 in future hooks run on non-leaders. In this way both leaders and
40 non-leaders can share handlers, waiting on these states.
41 '''
42 settings = settings or {}
43 settings.update(kw)
44 previous = unitdata.kv().getrange('leadership.settings.', strip=True)
45
46 for key, value in settings.items():
47 if value != previous.get(key):
48 reactive.set_state('leadership.changed.{}'.format(key))
49 reactive.set_state('leadership.changed')
50 reactive.helpers.toggle_state('leadership.set.{}'.format(key),
51 value is not None)
52 hookenv.leader_set(settings)
53 unitdata.kv().update(settings, prefix='leadership.settings.')
54
55
56def leader_get(attribute=None):
57 '''Return leadership settings, per charmhelpers.core.hookenv.leader_get.'''
58 return hookenv.leader_get(attribute)
059
=== modified file 'metadata.yaml'
--- metadata.yaml 2015-12-09 06:50:47 +0000
+++ metadata.yaml 2017-02-15 08:51:47 +0000
@@ -1,14 +1,19 @@
1name: ibm-mq1"name": "ibm-mq"
2summary: IBM MQ Messaging product2"summary": "IBM MQ messaging Product"
3maintainer: Juju Support <jujusupp@us.ibm.com>3"maintainer": "IBM Juju Support Team <jujusupp@us.ibm.com>"
4description: |4"description": |
5 IBM MQ provides for messaging services to transport multiple types of 5 IBM MQ provides for messaging services to transport multiple types of data.
6 data. 6"tags":
7tags:7- "ibm"
8 - misc8- "ibm"
9 - messaging9- "apt"
10 - ibm10- "leadership"
11subordinate: false11- "mq"
12provides:12- "misc"
13 messaging:13- "ibm"
14 interface: ibm-mq14- "messaging"
15"provides":
16 "messaging":
17 "interface": "ibm-mq"
18"min-juju-version": "2.0-beta1"
19"subordinate": !!bool "false"
1520
=== added directory 'reactive'
=== added file 'reactive/__init__.py'
=== added file 'reactive/apt.py'
--- reactive/apt.py 1970-01-01 00:00:00 +0000
+++ reactive/apt.py 2017-02-15 08:51:47 +0000
@@ -0,0 +1,131 @@
1# Copyright 2015-2016 Canonical Ltd.
2#
3# This file is part of the Apt layer for Juju.
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 3, as
7# published by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17'''
18charms.reactive helpers for dealing with deb packages.
19
20Add apt package sources using add_source(). Queue deb packages for
21installation with install(). Configure and work with your software
22once the apt.installed.{packagename} state is set.
23'''
24import subprocess
25
26from charmhelpers import fetch
27from charmhelpers.core import hookenv
28from charmhelpers.core.hookenv import WARNING
29from charms import layer
30from charms import reactive
31from charms.reactive import when, when_not
32
33import charms.apt
34
35
36@when('apt.needs_update')
37def update():
38 charms.apt.update()
39
40
41@when('apt.queued_installs')
42@when_not('apt.needs_update')
43def install_queued():
44 charms.apt.install_queued()
45
46
47@when_not('apt.queued_installs')
48def ensure_package_status():
49 charms.apt.ensure_package_status()
50
51
52def filter_installed_packages(packages):
53 # Don't use fetch.filter_installed_packages, as it depends on python-apt
54 # and not available if the basic layer's use_site_packages option is off
55 # TODO: Move this to charm-helpers.fetch
56 cmd = ['dpkg-query', '--show', r'--showformat=${Package}\n']
57 installed = set(subprocess.check_output(cmd,
58 universal_newlines=True).split())
59 return set(packages) - installed
60
61
62def clear_removed_package_states():
63 """On hook startup, clear install states for removed packages."""
64 removed = filter_installed_packages(charms.apt.installed())
65 if removed:
66 hookenv.log('{} missing packages ({})'.format(len(removed),
67 ','.join(removed)),
68 WARNING)
69 for package in removed:
70 reactive.remove_state('apt.installed.{}'.format(package))
71
72
73def configure_sources():
74 """Add user specified package sources from the service configuration.
75
76 See charmhelpers.fetch.configure_sources for details.
77 """
78 config = hookenv.config()
79
80 # We don't have enums, so we need to validate this ourselves.
81 package_status = config.get('package_status')
82 if package_status not in ('hold', 'install'):
83 charms.apt.status_set('blocked',
84 'Unknown package_status {}'
85 ''.format(package_status))
86 # Die before further hooks are run. This isn't very nice, but
87 # there is no other way to inform the operator that they have
88 # invalid configuration.
89 raise SystemExit(0)
90
91 sources = config.get('install_sources')
92 keys = config.get('install_keys')
93 if reactive.helpers.data_changed('apt.configure_sources', (sources, keys)):
94 fetch.configure_sources(update=False,
95 sources_var='install_sources',
96 keys_var='install_keys')
97 reactive.set_state('apt.needs_update')
98
99 extra_packages = sorted(config.get('extra_packages', '').split())
100 if extra_packages:
101 charms.apt.queue_install(extra_packages)
102
103
104def queue_layer_packages():
105 """Add packages listed in build-time layer options."""
106 # Both basic and apt layer. basic layer will have already installed
107 # its defined packages, but rescheduling it here gets the apt layer
108 # state set and they will pinned as any other apt layer installed
109 # package.
110 opts = layer.options()
111 for section in ['basic', 'apt']:
112 if section in opts and 'packages' in opts[section]:
113 charms.apt.queue_install(opts[section]['packages'])
114
115
116# Per https://github.com/juju-solutions/charms.reactive/issues/33,
117# this module may be imported multiple times so ensure the
118# initialization hook is only registered once. I have to piggy back
119# onto the namespace of a module imported before reactive discovery
120# to do this.
121if not hasattr(reactive, '_apt_registered'):
122 # We need to register this to run every hook, not just during install
123 # and config-changed, to protect against race conditions. If we don't
124 # do this, then the config in the hook environment may show updates
125 # to running hooks well before the config-changed hook has been invoked
126 # and the intialization provided an opertunity to be run.
127 hookenv.atstart(hookenv.log, 'Initializing Apt Layer')
128 hookenv.atstart(clear_removed_package_states)
129 hookenv.atstart(configure_sources)
130 hookenv.atstart(queue_layer_packages)
131 reactive._apt_registered = True
0132
=== added file 'reactive/ibm-base.sh'
--- reactive/ibm-base.sh 1970-01-01 00:00:00 +0000
+++ reactive/ibm-base.sh 2017-02-15 08:51:47 +0000
@@ -0,0 +1,107 @@
1#!/bin/bash
2source charms.reactive.sh
3set -e
4
5
6# Utility function to verify a downloaded resource
7# :param: file name
8# :param: checksum type
9# :param: checksum value
10function verify_curl_resource() {
11 local FILE=$1
12 local TYPE=$2
13 local EXPECTED_SUM=$3
14 local CALCULATED_SUM=""
15 local PROG=""
16
17 if [ ! -r ${FILE} ]; then
18 status-set blocked "ibm-base: could not read ${FILE}"
19 juju-log "Could not verify the downloaded resource. File could not be read: ${FILE}"
20 fi
21
22 # Set our checksum utility based on the requested type
23 case "${TYPE}" in
24 md5)
25 PROG='md5sum'
26 ;;
27 sha256)
28 PROG='sha256sum'
29 ;;
30 sha512)
31 PROG='sha512sum'
32 ;;
33 *)
34 status-set blocked "ibm-base: checksum type must be md5, sha215, or sha512"
35 juju-log "Could not verify the downloaded resource ${FILE}. Unknown checksum type: ${TYPE}"
36 return 1
37 esac
38
39 CALCULATED_SUM=`${PROG} ${FILE} | awk '{print $1}'`
40 if [ "${CALCULATED_SUM}" = "${EXPECTED_SUM}" ]; then
41 juju-log "Checksum verified for ${FILE}."
42 return 0
43 else
44 status-set blocked "ibm-base: checksums did not match"
45 juju-log "Checksum mismatch for ${FILE}. Expected ${EXPECTED_SUM}, got ${CALCULATED_SUM}"
46 return 1
47 fi
48}
49
50
51# Fetch curl resources if a URL is configured
52@when 'config.set.curl_url'
53@when_any 'config.new.curl_url' 'config.changed.curl_url' 'config.new.curl_opts' 'config.changed.curl_opts'
54function fetch_curl_resource() {
55 local ARCHIVE_DIR="${CHARM_DIR}/files/archives"
56 local CURL_URL=$(config-get 'curl_url')
57 local CURL_OPTS=$(config-get 'curl_opts')
58
59 status-set maintenance "ibm-base: fetching resource(s)"
60
61 mkdir -p ${ARCHIVE_DIR}
62 cd ${ARCHIVE_DIR}
63 # Multiple URLs may be separated by a space, so loop.
64 for URL_STRING in ${CURL_URL}
65 do
66 # For each URL_STRING, set the url, checksum type, and checksum value.
67 local URL=${URL_STRING%%\?*} # string before the first '?'
68 local FILE_NAME=${URL##*\/} # string after the last '/'
69 local SUM_STRING=${URL_STRING#*\?} # string after the first '?'
70 local SUM_TYPE=${SUM_STRING%%\=*} # string before the first '='
71 local SUM_VALUE=${SUM_STRING#*\=} # string after the first '='
72
73 if [ -z ${FILE_NAME} ]; then
74 FILE_NAME="juju-${RANDOM}"
75 fi
76 curl --silent --show-error ${CURL_OPTS} -o ${FILE_NAME} ${URL}
77
78 # Verify our resource checksum. If this fails, let verify_resource log
79 # the reason and exit successfully. Exiting non-zero would fail the hook,
80 # so return 0 and simply inform the user that verification failed.
81 verify_curl_resource ${FILE_NAME} ${SUM_TYPE} ${SUM_VALUE} || return 0
82 done
83 cd -
84
85 set_state 'ibm-base.curl.resource.fetched'
86 status-set active "ibm-base: curl resource(s) fetched"
87 juju-log 'Curl resource fetched'
88}
89
90
91# Handle license acceptance
92@when 'config.changed.license_accepted'
93function check_license_acceptance() {
94 local LIC_ACCEPTED=$(config-get 'license_accepted')
95
96 # compare lowercase LIC_ACCEPTED (requires bash > 4)
97 if [ "${LIC_ACCEPTED,,}" = "true" ]; then
98 set_state 'ibm-base.license.accepted'
99 juju-log 'License accepted'
100 else
101 remove_state 'ibm-base.license.accepted'
102 juju-log 'License NOT accepted'
103 fi
104}
105
106# Main reactive entry point
107reactive_handler_main
0108
=== added file 'reactive/ibm-mq.sh'
--- reactive/ibm-mq.sh 1970-01-01 00:00:00 +0000
+++ reactive/ibm-mq.sh 2017-02-15 08:51:47 +0000
@@ -0,0 +1,340 @@
1#!/bin/bash
2
3set -ex
4source charms.reactive.sh
5
6ARCHIVE_DIR=$CHARM_DIR/files/archives
7MQ_INSTALL_PATH=/opt/mqm
8ARCHITECTURE=`uname -m`
9RPM_INSTALL_ARG="-ivh --nodeps --force-debian"
10relatedService=""
11QMA=""
12QM_Name=""
13hostname=`unit-get private-address`
14 if [ "$ARCHITECTURE" = "ppc64le" ]; then
15 RPM_INSTALL_ARG="-ivh --nodeps --force-debian --ignorearch"
16 fi
17
18
19# Check whether IBM MQ is installed
20
21is_mq_installed()
22{
23 if [ -d $MQ_INSTALL_PATH/bin ]; then
24 source $MQ_INSTALL_PATH/bin/setmqenv -s
25 if [ $? == 0 ]; then
26 source $MQ_INSTALL_PATH/bin/setmqenv -s
27 echo "True"
28 fi
29 else
30 echo "False"
31 fi
32}
33
34# Remove MQ, if installed
35remove_software()
36{
37
38 mq_installed=`is_mq_installed`
39 if [ $mq_installed == True ]; then
40 juju-log "Removing IBM MQ software."
41 # Stop all queues
42 questr="QMNAME("
43 for queue in `$MQ_INSTALL_PATH/bin/dspmq -o installation | cut -d' ' -f1 `;
44 do
45 # Get the queue manager name
46 queue_mgr_name=${queue:${#questr}:${#queue}-${#questr}-1}
47 juju-log "Stopping queue manager $queue_mgr_name"
48 set +e
49 su -l mqm -c "$MQ_INSTALL_PATH/bin/endmqm -w $queue_mgr_name"
50 su -l mqm -c "$MQ_INSTALL_PATH/bin/endmqlsr -w -m $queue_mgr_name"
51 set -e
52 done
53 # Get list of packages and remove them
54 mq_rpms="`rpm -qa | grep MQSeries`"
55 juju-log "Removing package(s) $mq_rpms"
56 rpm -ev --force-debian $mq_rpms
57 fi
58
59}
60
61# Update system configuration after installing MQ
62configure_system()
63{
64 juju-log "IBM MQ: Updating system configuration."
65 # Some containers do not allow system updates.
66 # Prevent the script from failing in such cases
67 set +e
68
69 # Add user ubuntu to mqm group
70 adduser ubuntu mqm
71
72 # Update mqm file limits
73
74 hard_nf=`su - mqm -c "ulimit -Hn"`
75 soft_nf=`su - mqm -c "ulimit -Sn"`
76
77 if [ $hard_nf -lt 10240 -o $soft_nf -lt 10240 ]; then
78 if [ "`grep mqm /etc/security/limits.conf`"=="" ]; then
79 sed -i 's/# End of file/mqm hard nofile 10240\nmqm soft nofile 10240\n# End of file/' /etc/security/limits.conf
80 fi
81 #Update /etc/pam.d/common-session file to take effect the above changes
82 result_search=`grep 'session required pam_limits.so' /etc/pam.d/common-session`
83 if [ -z "$result_search" ]; then
84 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
85 fi
86 fi
87
88 set -e
89 juju-log "IBM MQ: Completed system configuration update."
90
91}
92
93# create Queue manager and Queue
94create_QM_Queue()
95{
96
97 juju-log "IBM MQ: Verifying installation."
98
99 relatedService=$1
100 consumer_hostname=$2
101
102 QMA="${relatedService//-}.queue.manager"
103
104 # Prevent the script from failing on failure.
105 # It could because configure_system call failed
106 set +e
107
108 # Run as mqm user as root will not work
109
110 # Create queue manager
111 juju-log "IBM MQ: Create queue manager $QMA."
112 su -l mqm -c "$MQ_INSTALL_PATH/bin/crtmqm $QMA"
113 rc=$?
114 if [ $rc == 0 ]; then
115 juju-log "IBM MQ: queue manager $QMA created."
116 QM_create=1
117 elif [ $rc == 8 ]; then
118 QM_exists=1
119 else
120 juju-log "IBM MQ: Failed to create queue manager $QMA."
121 fi
122
123 # Start queue manager
124 if [ $QM_create -eq 1 ]; then
125 juju-log "IBM MQ: Starting queue manager $QMA."
126 su -l mqm -c "$MQ_INSTALL_PATH/bin/strmqm $QMA"
127 if [ $? == 0 ]; then
128 juju-log "IBM MQ: Queue manager $QMA started."
129 else
130 juju-log "IBM MQ: Failed to start queue manager $QMA."
131
132 fi
133
134 # set the port to listen queue manager
135 new_port=1414
136 free_port=0
137 is_free=0
138
139 #Check whether the port number is already in use
140 while [ $free_port -eq 0 ]
141 do
142
143 #is_free=`netstat -lnp | grep $new_port | cut -d":" -f2 | cut -d" " -f1`
144 is_free=`netstat -an | grep $new_port | grep LISTEN`
145 if [ -z "$is_free" ]; then
146 free_port=1
147 else
148 new_port=$((new_port+1))
149 fi
150
151 done
152
153 juju-log "IBM MQ: Setting port to $new_port"
154
155 #start queue manager in listener mode
156 juju-log "IBM MQ: Starting queue manager $QMA in listener mode"
157 su -l mqm -c "$MQ_INSTALL_PATH/bin/runmqlsr -m $QMA -t tcp -p $new_port" &
158 if [ $? == 0 ]; then
159 juju-log "IBM MQ: Queue manager $QMA listening on $new_port"
160 else
161 juju-log "IBM MQ: Queue manager $QMA failed to listen"
162 fi
163
164 # Create queue and setup the queue configuration for consumer service
165 juju-log "IBM MQ: Creating queue."
166
167 sudo bash -c "cat > $CHARM_DIR/files/archives/${relatedService//-}_mq_create_queue.mqsc << EOF
168DEFINE QLOCAL (QUEUE1)
169DEFINE CHANNEL ('MDB.SVRCONN') CHLTYPE(SVRCONN) TRPTYPE(TCP)
170SET CHLAUTH ('MDB.SVRCONN') TYPE(ADDRESSMAP) ADDRESS($consumer_hostname) MCAUSER('mqm')
171SET AUTHREC OBJTYPE(QMGR) PRINCIPAL('mqm') AUTHADD(CONNECT,INQ)
172SET AUTHREC PROFILE('QUEUE1') OBJTYPE(QUEUE) PRINCIPAL('mqm') AUTHADD(PUT,GET,INQ,BROWSE)
173SET AUTHREC PROFILE('SYSTEM.BASE.TOPIC') OBJTYPE(TOPIC) PRINCIPAL('mqm') AUTHADD(PUB, SUB)
174EOF"
175 if [ $? == 0 ]; then
176 # Create file to delete the above queue when relation breaks
177 sudo bash -c "echo 'DELETE QLOCAL(QUEUE1) PURGE' > $CHARM_DIR/files/archives/${relatedService//-}_mq_delete_queue.mqsc"
178 fi
179
180 if [ -f $CHARM_DIR/files/archives/${relatedService//-}_mq_create_queue.mqsc ]; then
181
182 su -l mqm -c "$MQ_INSTALL_PATH/bin/runmqsc $QMA < $CHARM_DIR/files/archives/${relatedService//-}_mq_create_queue.mqsc"
183 if [ $? == 0 ]; then
184 juju-log "IBM MQ: Queue created."
185 else
186 juju-log "IBM MQ: Failed to create queue."
187 fi
188
189 QM_Name=$QMA
190 Qname=$CHARM_DIR/files/archives/${relatedService//-}_mq_create_queue.mqsc
191 port=$new_port
192 open-port $port/TCP
193
194 juju-log "create_QM_Queue values are QM_name=$QM_Name Qname=$Qname hostname=$hostname port=$port"
195 fi
196 elif [ $QM_exists -eq 1 ];then
197 QM_Name=$QMA
198 Qname=$CHARM_DIR/files/archives/${relatedService//-}_mq_create_queue.mqsc
199 port=`ps -ef| grep runmqlsr | grep "$QMA" | grep -v grep | grep "su -l mqm" | awk -F"-p " '{print $2}'`
200 juju-log "relation with ${relatedService} already exists"
201 juju-log "create_QM_Queue values are QM_name=$QM_Name Qname=$Qname hostname=$hostname port=$port"
202 fi
203
204}
205
206
207# IBM MQ: Deleting queue
208delete_QM_Queue()
209{
210
211 relatedService=$1
212
213 QMA="${relatedService//-}.queue.manager"
214 # Delete queue
215 juju-log "IBM MQ: Deleting queue."
216 su -l mqm -c "$MQ_INSTALL_PATH/bin/runmqsc $QMA < $CHARM_DIR/files/archives/${relatedService//-}_mq_delete_queue.mqsc"
217 if [ $? == 0 ]; then
218 juju-log "IBM MQ: Deleted queue."
219 else
220 juju-log "IBM MQ: Failed to delete queue."
221 fi
222
223
224 port=`ps -ef| grep runmqlsr | grep "$QMA" | grep -v grep | grep "su -l mqm" | awk -F"-p " '{print $2}'`
225
226 juju-log "IBM MQ: stop Queue manager from listening on port"
227 su -l mqm -c "$MQ_INSTALL_PATH/bin/endmqlsr -m $QMA -w"
228 if [ $? == 0 ]; then
229 juju-log "IBM MQ: Stopped queue manager from listening."
230 else
231 juju-log "IBM MQ: Failed to stop queue manager to listen."
232 fi
233
234 #close-port the open-port for the consumer
235 close-port $port
236
237 # End queue manger
238 juju-log "IBM MQ: Stopping queue manager."
239 su -l mqm -c "$MQ_INSTALL_PATH/bin/endmqm $QMA"
240 su -l mqm -c "$MQ_INSTALL_PATH/bin/endmqlsr -w -m $QMA"
241 if [ $? -eq 0 ]; then
242 juju-log "IBM MQ: Queue Manager is stopped."
243 else
244 juju-log "IBM MQ: Queue Manager failed to stop."
245 fi
246
247 sleep 10
248
249 juju-log "IBM MQ: Deleting queue manager."
250 su -l mqm -c "$MQ_INSTALL_PATH/bin/dltmqm $QMA"
251 if [ $? -eq 0 ]; then
252 juju-log "IBM MQ: Queue Manager is deleted."
253 else
254 juju-log "IBM MQ: Queue Manager could not be deleted."
255 fi
256
257 #clean up the created MQSC files
258 if [ -f $CHARM_DIR/files/archives/${relatedService//-}_mq_create_queue.mqsc ];then
259 sudo bash -c "rm $CHARM_DIR/files/archives/${relatedService//-}_mq_create_queue.mqsc"
260 fi
261 if [ -f $CHARM_DIR/files/archives/${relatedService//-}_mq_delete_queue.mqsc ]; then
262 sudo bash -c "rm $CHARM_DIR/files/archives/${relatedService//-}_mq_delete_queue.mqsc"
263 fi
264
265 set -e
266 juju-log "IBM MQ: Deleting queue completed."
267}
268
269@when 'ibm-base.license.accepted' 'ibm-base.curl.resource.fetched'
270@when_not 'ibm-mq.installed'
271function mq_install() {
272 juju-log "inside mq install function."
273 mq_inst=`is_mq_installed`
274 juju-log "mq_inst==$mq_inst"
275 if [ -f $CHARM_DIR/files/archives/*.gz ]; then
276 juju-log "IBM MQ: Extracting IBM MQ package."
277 tar xvfz $CHARM_DIR/files/archives/*.gz --strip-components=1 -C $CHARM_DIR/files/archives
278 rm -rf $CHARM_DIR/files/archives/*.gz
279 juju-log "IBM MQ: Extracted IBM MQ package."
280 juju-log "Awaiting acceptance of IBM MQ license (see README on how to accept the license)."
281 fi
282 if [ $mq_inst == False ]; then
283 # Check MQ package availability
284 if [ -f $CHARM_DIR/files/archives/MQSeriesServer*rpm ] && [ -f $CHARM_DIR/files/archives/MQSeriesRuntime*rpm ];
285 then
286 echo "MQ Packages available for installation.";
287 $CHARM_DIR/files/archives/mqlicense.sh -accept
288 juju-log "Installing available MQ packages."
289 rpm $RPM_INSTALL_ARG --prefix $MQ_INSTALL_PATH $CHARM_DIR/files/archives/MQSeries*rpm
290 juju-log "Installation of available MQ packages complete."
291 # Configure system values for MQ
292 configure_system
293 set_state 'ibm-mq.installed'
294 status-set active "MQ installed successfully"
295 else
296 status-set blocked "MQ Packages missing. Please check README file Upgrade MQ charm after adding the MQ packages"
297 fi
298 fi
299}
300
301@when 'ibm-mq.installed'
302@when 'messaging.connected'
303function export_mq_details(){
304 juju-log "on ibm_mq.connected triggered here"
305 services=$(relation_call --state=messaging.connected services) || true
306 for service in $services; do
307 juju-log "$service"
308 consumer_hostname=$(relation_call --state=messaging.connected get_consumer_hostname) || true
309 create_QM_Queue $service $consumer_hostname
310 juju-log "create_QM_Queue values are QM_name=$QM_Name Qname=$Qname hostname=$hostname port=$port"
311 relation_call --state=messaging.connected set_mq_details $service $QM_Name $Qname $hostname $port || true
312 done
313}
314
315@when 'messaging.departed' 'ibm-mq.installed'
316function remove_mq_QM_Q() {
317 juju-log "in delete_QM_Queue.."
318 services=$(relation_call --state=messaging.departed services) || true
319 for service in $services; do
320 juju-log "$service"
321 delete_QM_Queue $service
322 juju-log "delete_QM_Queue done."
323 juju-log "Calling dismiss for $service"
324 relation_call --state=messaging.departed dismiss $service || true
325 done
326 }
327
328@when 'ibm-mq.installed'
329@when_not 'ibm-base.license.accepted'
330function uninstall() {
331 # Uninstall all versions of IBM MQ
332 status-set maintenance "Uninstalling IBM MQ"
333 mq_license_accepted=`config-get accept-ibm-mq-license`
334 remove_software
335 remove_state 'ibm-mq.installed'
336 remove_state 'messaging.connected'
337 status-set blocked "IBM MQ Uninstalled"
338}
339
340reactive_handler_main
0341
=== added file 'reactive/leadership.py'
--- reactive/leadership.py 1970-01-01 00:00:00 +0000
+++ reactive/leadership.py 2017-02-15 08:51:47 +0000
@@ -0,0 +1,68 @@
1# Copyright 2015-2016 Canonical Ltd.
2#
3# This file is part of the Leadership Layer for Juju.
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 3, as
7# published by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17from charmhelpers.core import hookenv
18from charmhelpers.core import unitdata
19
20from charms import reactive
21from charms.leadership import leader_get, leader_set
22
23
24__all__ = ['leader_get', 'leader_set'] # Backwards compatibility
25
26
27def initialize_leadership_state():
28 '''Initialize leadership.* states from the hook environment.
29
30 Invoked by hookenv.atstart() so states are available in
31 @hook decorated handlers.
32 '''
33 is_leader = hookenv.is_leader()
34 if is_leader:
35 hookenv.log('Initializing Leadership Layer (is leader)')
36 else:
37 hookenv.log('Initializing Leadership Layer (is follower)')
38
39 reactive.helpers.toggle_state('leadership.is_leader', is_leader)
40
41 previous = unitdata.kv().getrange('leadership.settings.', strip=True)
42 current = hookenv.leader_get()
43
44 # Handle deletions.
45 for key in set(previous.keys()) - set(current.keys()):
46 current[key] = None
47
48 any_changed = False
49 for key, value in current.items():
50 reactive.helpers.toggle_state('leadership.changed.{}'.format(key),
51 value != previous.get(key))
52 if value != previous.get(key):
53 any_changed = True
54 reactive.helpers.toggle_state('leadership.set.{}'.format(key),
55 value is not None)
56 reactive.helpers.toggle_state('leadership.changed', any_changed)
57
58 unitdata.kv().update(current, prefix='leadership.settings.')
59
60
61# Per https://github.com/juju-solutions/charms.reactive/issues/33,
62# this module may be imported multiple times so ensure the
63# initialization hook is only registered once. I have to piggy back
64# onto the namespace of a module imported before reactive discovery
65# to do this.
66if not hasattr(reactive, '_leadership_registered'):
67 hookenv.atstart(initialize_leadership_state)
68 reactive._leadership_registered = True
069
=== added file 'reactive/test'
--- reactive/test 1970-01-01 00:00:00 +0000
+++ reactive/test 2017-02-15 08:51:47 +0000
@@ -0,0 +1,1 @@
1DEFINE QLOCAL (QUEUE1)
02
=== added file 'requirements.txt'
--- requirements.txt 1970-01-01 00:00:00 +0000
+++ requirements.txt 2017-02-15 08:51:47 +0000
@@ -0,0 +1,2 @@
1flake8
2pytest
03
=== modified file 'revision'
--- revision 2015-03-11 19:52:30 +0000
+++ revision 2017-02-15 08:51:47 +0000
@@ -1,1 +1,1 @@
1111
2\ No newline at end of file2\ No newline at end of file
33
=== modified file 'tests/00-setup'
--- tests/00-setup 2015-06-10 10:41:46 +0000
+++ tests/00-setup 2017-02-15 08:51:47 +0000
@@ -1,5 +1,17 @@
1#!/bin/bash1#!/bin/bash
2MQ_CURL_URL=${MQ_CURL_URL?Error: IBM MQ curl_url be defined in tests/00-setup}
3MQ_CURL_OPTS=${MQ_CURL_OPTS?Error: IBM MQ curl_OPTS be defined in tests/00-setup}
4MQ_LICENSE=${MQ_LICENSE?Error: IBM MQ License accepted value must be defined in tests/00-setup}
5
6# Add a local configuration file
7cat << EOF > local.yaml
8mq:
9 mq_curl_url: "$MQ_CURL_URL"
10 mq_curl_opts: "$MQ_CURL_OPTS"
11 mq_license_accepted: "$MQ_LICENSE"
12EOF
213
3sudo add-apt-repository ppa:juju/stable -y14sudo add-apt-repository ppa:juju/stable -y
4sudo apt-get update15sudo apt-get update
5sudo apt-get install amulet -y16sudo apt-get -y install unzip
17sudo apt-get install amulet python3 -y
618
=== modified file 'tests/10-bundles-test.py'
--- tests/10-bundles-test.py 2015-07-15 08:13:51 +0000
+++ tests/10-bundles-test.py 2017-02-15 08:51:47 +0000
@@ -1,11 +1,11 @@
1#!/usr/bin/env python31#!/usr/bin/env python3
2
3# This amulet test deploys the bundles.yaml file in this directory.2# This amulet test deploys the bundles.yaml file in this directory.
43
5import os4import os
6import unittest5import unittest
7import yaml6import yaml
8import amulet7import amulet
8import sys
99
10# Lots of prereqs on this charm (eg: java), so give it a large timeout10# Lots of prereqs on this charm (eg: java), so give it a large timeout
11seconds_to_wait = 150011seconds_to_wait = 1500
@@ -16,14 +16,41 @@
16 @classmethod16 @classmethod
17 def setUpClass(cls):17 def setUpClass(cls):
18 """ Set up an amulet deployment using the bundle. """18 """ Set up an amulet deployment using the bundle. """
19 d = amulet.Deployment()19 d = amulet.Deployment(juju_env='local', series='trusty')
20 bundle_path = os.path.join(os.path.dirname(__file__), 'bundles.yaml')20 local_path = os.path.join(os.path.dirname(__file__), 'local.yaml')
21 with open(bundle_path, 'r') as bundle_file:21 with open(local_path, "r") as fd:
22 contents = yaml.safe_load(bundle_file)22 config = yaml.safe_load(fd)
23 d.load(contents)23 curl_url = config.get('mq').get('mq_curl_url')
24 print('Using mq_curl_url %s' % curl_url)
25 # Test if a IBM mq curl url is defined
26 if not curl_url:
27 print("You need to define a im_curl_url.\n"
28 "Edit local.yaml or tests/00-setup and run it again.")
29 sys.exit(1)
30
31 curl_opts = config.get('mq').get('mq_curl_opts')
32 print('Using mq_curl_opts %s' % curl_opts)
33 # Test if IBM curl_opts is defined
34 if not curl_opts:
35 print("You need to define a curl_opts."
36 " repository.\n Edit local.yaml or tests/00-setup"
37 " and run it again.")
38 sys.exit(1)
39 license_accepted = config.get('mq').get('mq_license_accepted')
40 print('Using license_accepted %s' % license_accepted)
41 # Test if a license_accepted is defined
42 if not license_accepted:
43 print("You need to define a license_accepted for the IBM MQ"
44 " product repository.\n Edit local.yaml or tests/00-setup"
45 " and run it again.")
46 sys.exit(1)
47
48 d.add('ibm-mq')
2449
25 # Software doesn't actually install until you accept the license50 # Software doesn't actually install until you accept the license
26 d.configure('ibm-mq', {'accept-ibm-mq-license': True})51 d.configure('ibm-mq', {'license_accepted': license_accepted,
52 'curl_url': curl_url,
53 'curl_opts': curl_opts})
2754
28 d.setup(seconds_to_wait)55 d.setup(seconds_to_wait)
29 d.sentry.wait(seconds_to_wait)56 d.sentry.wait(seconds_to_wait)
@@ -34,22 +61,26 @@
34 self.assertTrue(self.d.deployed)61 self.assertTrue(self.d.deployed)
3562
36 def test_running(self):63 def test_running(self):
37 """ Test that, if deployed, everything is set up and running correctly. """64 """ Test that, if deployed,
38 ibm_mq_unit = self.d.sentry.unit['ibm-mq/0']65 everything is set up and running correctly. """
66 ibm_mq_unit = self.d.sentry.unit['ibm-mq'][0]
3967
40 # Configure system values68 # Configure system values
41 output, code = ibm_mq_unit.run('sysctl kernel.shmmax | cut -d= -f2')69 output, code = ibm_mq_unit.run('sysctl kernel.shmmax | cut -d= -f2')
42 print(output)70 print(output)
43 if code != 0 or int(output) < 268435456:71 if code != 0 or int(output) < 268435456:
44 # this isn't really a pass/fail test, as it depends on the environment72 # this isn't really a pass/fail test, as it depends
45 # to which the charm is deployed, and doesn't block the install73 # on the environment to which
46 print('Unable to set kernel.shmmax value (perhaps on local provider?).')74 # the charm is deployed, and doesn't block the install
75 print('Unable to set kernel.shmmax'
76 ' value (perhaps on local provider?).')
4777
48 output, code = ibm_mq_unit.run('sysctl fs.file-max | cut -d= -f2')78 output, code = ibm_mq_unit.run('sysctl fs.file-max | cut -d= -f2')
49 print(output)79 print(output)
50 if code != 0 or int(output) < 524288:80 if code != 0 or int(output) < 524288:
51 # this isn't really a pass/fail test, as it depends on the environment81 # this isn't really a pass/fail test, as it depends
52 # to which the charm is deployed, and doesn't block the install82 # on the environment to which
83 # the charm is deployed, and doesn't block the install
53 print('Unable to set fs.file value (perhaps on local provider?).')84 print('Unable to set fs.file value (perhaps on local provider?).')
5485
55 output, code = ibm_mq_unit.run('sysctl -p')86 output, code = ibm_mq_unit.run('sysctl -p')
5687
=== removed file 'tests/bundles.yaml'
--- tests/bundles.yaml 2015-07-09 18:38:01 +0000
+++ tests/bundles.yaml 1970-01-01 00:00:00 +0000
@@ -1,10 +0,0 @@
1ibm-mq-liberty-bundle:
2 services:
3 "ibm-mq":
4 charm: "ibm-mq"
5 num_units: 1
6 annotations:
7 "gui-x": "300"
8 "gui-y": "300"
9 relations: []
10 series: trusty
110
=== added file 'tox.ini'
--- tox.ini 1970-01-01 00:00:00 +0000
+++ tox.ini 2017-02-15 08:51:47 +0000
@@ -0,0 +1,12 @@
1[tox]
2skipsdist=True
3envlist = py34, py35
4skip_missing_interpreters = True
5
6[testenv]
7commands = py.test -v
8deps =
9 -r{toxinidir}/requirements.txt
10
11[flake8]
12exclude=docs
013
=== added directory 'wheelhouse'
=== added file 'wheelhouse/Jinja2-2.8.tar.gz'
1Binary 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 differ14Binary 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
=== added file 'wheelhouse/MarkupSafe-0.23.tar.gz'
2Binary 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 differ15Binary 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
=== added file 'wheelhouse/PyYAML-3.12.tar.gz'
3Binary 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 differ16Binary 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
=== added file 'wheelhouse/Tempita-0.5.2.tar.gz'
4Binary 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 differ17Binary 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
=== added file 'wheelhouse/charmhelpers-0.9.1.tar.gz'
5Binary 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 differ18Binary 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
=== added file 'wheelhouse/charms.reactive-0.4.5.tar.gz'
6Binary 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 differ19Binary 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
=== added file 'wheelhouse/netaddr-0.7.18.tar.gz'
7Binary 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 differ20Binary 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
=== added file 'wheelhouse/pip-8.1.2.tar.gz'
8Binary 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 differ21Binary 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
=== added file 'wheelhouse/pyaml-16.9.0.tar.gz'
9Binary 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 differ22Binary 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
=== added file 'wheelhouse/six-1.10.0.tar.gz'
10Binary 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 differ23Binary 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: