Merge lp:~chad.smith/charms/precise/postgresql/postgresql-using-storage-subordinate into lp:charms/postgresql

Proposed by Chad Smith
Status: Merged
Approved by: Stuart Bishop
Approved revision: 102
Merged at revision: 98
Proposed branch: lp:~chad.smith/charms/precise/postgresql/postgresql-using-storage-subordinate
Merge into: lp:charms/postgresql
Diff against target: 961 lines (+313/-457)
6 files modified
README.md (+27/-0)
config.yaml (+0/-31)
hooks/hooks.py (+188/-193)
hooks/test_hooks.py (+94/-13)
metadata.yaml (+4/-0)
scripts/volume-common.sh (+0/-220)
To merge this branch: bzr merge lp:~chad.smith/charms/precise/postgresql/postgresql-using-storage-subordinate
Reviewer Review Type Date Requested Status
Stuart Bishop (community) Approve
Chad Smith (community) Approve
charmers Pending
Review via email: mp+206078@code.launchpad.net

Description of the change

Both storage subordinate charm and block storage broker are now live in the charm store. I'm unblocking this review now that the storage and block-storage-subordinate are in the charmstore. The bundle described below is updated to point to the public approved charm branches.

----------------------
There is a new storage subordinate charm that has incorporated all storage mount,fsck and partitioning that the postgresql charm performed as well as talking to the block-storage-broker charm to automatically create, attach. reattach or detach nova volumes or nfs shares on behalf of your principal postgresql units.

The storage subordinate attempts to avoid duplication of partitioning, mounting and attaching devices in multiple charms by coalescing all that work in one charm. Only thing that the principal (postgresql in this case) has to provide is a preferred mountpoint to the storage subordinate.

This branch does the following:
1. adds the data relation to metadata.yaml for communicating with the storage subordinate to request a mountpoint.
2. drops all postgresql scripts/code used for discovery, partitioning, fsck and mount of attached nova volumes in favor of using the consolidated storage subordinate charm.
3. adds a new config option storage_mount_point (which defaults to /mnt/storage).
4. drops the config options volume-ephemeral-storage and volume-map as volume-map can be specified in the storage subordinate and ephemeral=false is inferred because of a relation with storage subordinate
5. calls posgresql_stop() on data-relation-departed to shutdown postgresql services if our persistent volume mount disapears
6. some more unit tests for code coverage

To deploy and test without any nova volumes created:

$ cat >postgresql-storage-bundle.cfg <<END
common:
    services:
        postgresql:
            branch: lp:~chad.smith/charms/precise/postgresql/postgresql-using-storage-subordinate
            constraints: mem=2048
            options:
                extra-packages: python-apt postgresql-contrib postgresql-9.1-debversion
                max_connections: 500
        block-storage-broker:
            branch: lp:~charmers/charms/precise/block-storage-broker/trunk
            options:
                key: <YOUR_OS_USERNAME>
                endpoint: https://keystone.canonistack.canonical.com:443/v2.0/
                region: <YOUR_OS_REGION_NAME>
                secret: <YOUR_OS_PASSWORD>
                tenant: <YOUR_OS_TENANT_NAME>

doit-no-nova-volume:
    inherits: common
    series: precise
    services:
        storage:
            branch: lp:~charmers/charms/precise/storage/trunk
            options:
                provider: block-storage-broker
                volume_size: 9
    relations:
        - [postgresql, storage]
        - [storage, block-storage-broker]

doit-with-existing-nova-volume:
    inherits: common
    series: precise
    services:
        storage:
            branch: lp:~charmers/charms/precise/storage/trunk
            options:
                provider: block-storage-broker
                volume_size: 9
                volume_map: "{postgresql/0: YOUR-POSTGRES-NOVA-VOLUME-ID}"

    relations:
        - [postgresql, storage]
        - [storage, block-storage-broker]

END

# The following will deploy postgresql related to storage and storage related to block-storage-broker and will create a volume for each postgres unit you create
$ juju-deployer -c postgresql-storage-bundle.cfg doit-no-nova-volume

# The following will deploy postgresql related to storage and storage related to block-storage-broker and will use volume-map to attach the pre-created YOUR-NOVA-VOLUME-ID to your postgresql/0 instance. Any postgresql unit not in volume-map will have a new volume created for it.
$ juju-deployer -c postgresql-storage-bundle.cfg doit-with-existing-nova-volume

To post a comment you must log in.
Revision history for this message
Stuart Bishop (stub) wrote :
Download full text (15.3 KiB)

On 15 February 2014 06:34, Chad Smith <email address hidden> wrote:

> There is a new storage subordinate charm that has incorporated all storage mount,fsck and partitioning that the postgresql charm performed as well as talking to the block-storage-broker charm to automatically create, attach. reattach or detach nova volumes or nfs shares on behalf of your principal postgresql units.
>
> The storage subordinate attempts to avoid duplication of partitioning, mounting and attaching devices in multiple charms by coalescing all that work in one charm. Only thing that the principal (postgresql in this case) has to provide is a preferred mountpoint to the storage subordinate.

Thanks for this. I think this is a good approach.

How is this branch handing upgrading of existing installation? Do we
add a subordinate using the existing disk and mount point, or do we
add a subordinate to use a new mount and migrate data from old-mount
as if it was local disk?

I do think this branch does not go far enough. I would like to see
more storage related code pushed into the subordinate. I believe that
with juju-run (in stable in ~2 weeks I'm told), we can remove *all*
storage related code from the parent charm, lowering the bar or
removing the barrier entirely for charms to use the subordinate
storage service.

For example, the PostgreSQL charm could do nothing except define a
storage-relation-joined hook that simply does:

    relation-set storage-path=/var/lib/postgresql/9.1/main

The subordinate charm would be responsible for:

    create storage
    mount storage on /mnt/juju-storage-tmp
    juju-run parent-unit hooks/stop
    rsync -a $storage-path /mnt/juju-storage-tmp
    unmount /mnt/juju-storage-tmp
    mv $storage-path $storage-path.juju-`date`
    mount storage on $storage-path
    juju-run parent-unit hooks/start

Perhaps start/stop isn't enough and we need support in the parent
charm's storage-relation-joined and storage-relation-changed hooks to
disable the service and reenable it when done (eg. if
db-relation-joined hooks get fired while the subordinate is being
setup, PG could get restarted at the wrong time).

If this was possible, we could add multiple subordinates easily to the
primary service. For example, 1 for the main database and additional
ones for any tablespaces that have been created and are being used.

> === modified file '.bzrignore'
> --- .bzrignore 2012-10-30 16:41:12 +0000
> +++ .bzrignore 2014-02-13 02:24:52 +0000
> @@ -0,0 +1,1 @@
> +hooks/_trial_temp

This is already in place on trunk. There is at least one change on
trunk that affects this work, which I'll comment on later.

> === modified file 'config.yaml'
> --- config.yaml 2014-01-22 07:09:38 +0000
> +++ config.yaml 2014-02-13 02:24:52 +0000
> @@ -277,36 +277,6 @@
> default: 4.0
> type: float
> description: Random page cost
> - #------------------------------------------------------------------------
> - # Volume management
> - # volume-map, volume-dev_regexp are only used
> - # if volume-ephemeral-storage == False
> - #------------------------------------------------------------------------
> - volume-ephemeral-storage:

We need to su...

Revision history for this message
Stuart Bishop (stub) :
review: Needs Information
Revision history for this message
Stuart Bishop (stub) wrote :

On 17 February 2014 21:27, Stuart Bishop <email address hidden> wrote:
> On trunk you will discover that postgresql_stop() never fails. It is
> important here, so I suggest we add an extra check at the end of
> postgresql_stop() that raises an exception if postgresql_is_running()
> still returns true.

Minor correction. If postgresql_stop fails, a
subprocess.CalledProcessException should be raised. The extra check
could still be good though to double check that pg_ctlcluster did what
it claims.

Revision history for this message
Chad Smith (chad.smith) wrote :

Stuart, Thanks for the comments. We discussed this week and agree with you about general direction and trying to incorporate the juju-run functionality when it is released. Since it's not going to be released in 1.16 we'd like to start with this approach and then have a followup that will simplify principal hook requirements even more in the next storage release.

As per the upgrade-charm path, here's what we are thinking (and I'll try incorporating that into this patch when I'm back on Tuesday).

Since upgrade-charm involves having to deploy storage service with a new configuration and add-relation, we can't do all this inside the upgrade-charm hook. So, we'll document the upgrade process in the README and ensure that the upgrade-charm hook exits(1) to get the attention of the juju admin.

 upgrade-charm will:
   1. stop postgresql,
   2. umount the mounted /srv/juju/vol-id
   3. unlink /var/lib/juju
   4. sys.exit(1) # Expose an upgrade-charm hook error
      -- a hook error occurs with config-change errors if you manipulate either volume-map or volume-ephemeral-storage or add & remove postgresql units when volume-map is in place

   5. Log a WARNING to the user in the postgresql-unit.log
WARNING: postgresql/1 unit has external volume id 123-123-12312-312 mounted via the
deprecated volume-map and volume-ephemeral-storage
configuration parameters.
These parameters are no longer available in the postgresql
charm in favor of using the volume_map parameter in the
storage subordinate charm.
The attached volume is left intact. To remount this
volume_id using the storage subordinate follow these steps:
1. cat > storage.cfg <<EOF\nstorage:
storage:\n"
  volume_map: "{postgresql/0: 123-123-12312-312, postgresql/1: 54-645-645-645}"
EOF
2. juju deploy --config storage.cfg storage
3. juju add-relation postgresql storage

How does an approach like this sound Stuart, given that the upgrade-charm hook can't deploy and relate new services?

Revision history for this message
Stuart Bishop (stub) wrote :
Download full text (3.7 KiB)

On 20 February 2014 07:21, Chad Smith <email address hidden> wrote:
> Stuart, Thanks for the comments. We discussed this week and agree with you about general direction and trying to incorporate the juju-run functionality when it is released. Since it's not going to be released in 1.16 we'd like to start with this approach and then have a followup that will simplify principal hook requirements even more in the next storage release.

Ok.

> As per the upgrade-charm path, here's what we are thinking (and I'll try incorporating that into this patch when I'm back on Tuesday).
>
> Since upgrade-charm involves having to deploy storage service with a new configuration and add-relation, we can't do all this inside the upgrade-charm hook. So, we'll document the upgrade process in the README and ensure that the upgrade-charm hook exits(1) to get the attention of the juju admin.
>
> upgrade-charm will:
> 1. stop postgresql,
> 2. umount the mounted /srv/juju/vol-id
> 3. unlink /var/lib/juju
> 4. sys.exit(1) # Expose an upgrade-charm hook error
> -- a hook error occurs with config-change errors if you manipulate either volume-map or volume-ephemeral-storage or add & remove postgresql units when volume-map is in place
>
> 5. Log a WARNING to the user in the postgresql-unit.log
> WARNING: postgresql/1 unit has external volume id 123-123-12312-312 mounted via the
> deprecated volume-map and volume-ephemeral-storage
> configuration parameters.
> These parameters are no longer available in the postgresql
> charm in favor of using the volume_map parameter in the
> storage subordinate charm.
> The attached volume is left intact. To remount this
> volume_id using the storage subordinate follow these steps:
> 1. cat > storage.cfg <<EOF\nstorage:
> storage:\n"
> volume_map: "{postgresql/0: 123-123-12312-312, postgresql/1: 54-645-645-645}"
> EOF
> 2. juju deploy --config storage.cfg storage
> 3. juju add-relation postgresql storage
>
>
> How does an approach like this sound Stuart, given that the upgrade-charm hook can't deploy and relate new services?

This sounds reasonable. Assuming the directory structures match, no
large database files need to be shuffled around. I am unsure if the
last task in step 5 will actually work, as the operator needs to add
the relation but upgrade-charm has not yet succeeded. Assuming this
works, how does the rest of the process go? After the operator has
performed the tasks given by step 5, we have a PostgreSQL unit in a
failed state and a subordinate charm with pending relation-hooks. Will
'juju resolved' or 'juju resolved --retry' be all that is required?

An alternative would be to not have upgrade-charm fail, but instead
set a flag ensuring that PG will not start up until the subordinate
storage charm has been successfully related. Disable autostart,
local_state could store the flag, and postgrersql_start would check
for it. It might not be this simple though, as other hooks like
config-changed would likely fail instead when PG fails to start on
request and I think config-changed will get invoked immediately after
the upgrade-charm...

And another alternative.... upgrade-charm just leaves the stora...

Read more...

Revision history for this message
Chad Smith (chad.smith) wrote :
Download full text (5.2 KiB)

Hi Stuart,
 Thanks for the suggestions here about the upgrade-charm lifecycle. What I ended up doing yesterday was the following to ease the upgrade process

upgrade_charm hook will:
 1. stop postgresql;
 2. umount the /srv/juju/vol-id;
 3. unlink /var/lib/postgresql/9.1/main;
 4. mount LABEL=vol-id /srv/data;
 5. symlink /var/lib/postgresql/9.1/main -> /src/data/postgresql/9.1/main
 6. chown -h postgres:postgresql /var/lib/postgresql/9.1/main
 7. log a warning about manual procedure to add storage and block-storage-broker services to ensure ongoing use of existing storage.volume_map that looks like the following:

WARNING: postgresql/0 unit has external volume id 123-123-123-123 mounted via the
deprecated volume-map and volume-ephemeral-storage configuration parameters.
These parameters are no longer available in the postgresql
charm in favor of using the volume_map parameter in the
storage subordinate charm.
We are migrating the attached volume to a mount path which
can be managed by the storage subordinate charm. To
continue using this volume_id with the storage subordinate
follow this procedure.
-----------------------------------
1. cat > storage.cfg <<EOF
storage:"
  provider:block-storage-broker
  root: /srv/data
  volume_map: "{postgresql/0: 123-123-123-123}"
EOF
2. juju deploy --config storage.cfg storage
3. juju deploy block-storage-broker
4. juju add-relation block-storage-broker storage
5. juju resolved --retry postgresql/0
6. juju add-relation postgresql storage
-----------------------------------

Remount and restart success for this external volume.
This current running installation will break upon
add/remove postgresql units or reations if you do not
follow the above procedure to ensure your external
volumes are preserved by the storage subordinate charm.

I also just added the ability to handle partitioned volumes into the storage subordinate charm as well. This was needed because the current postgresql charm partitions the volume into a single partition of 100% of the volume size, but the storage subordinate just uses the entire volume without partitioning.

>
> This sounds reasonable. Assuming the directory structures match, no
> large database files need to be shuffled around. I am unsure if the
> last task in step 5 will actually work,
Good point Stuart, as I found too, it didn't work. Even with the resolved --retry, it would still fail at the following config_changed hook that fires after the upgrade_charm hook because I had originally left PG in an unusable state (with the intent of allowing the storage subordinate relation to setup the mounts again). So, I've made sure all mount/link work is now done in the upgrade_charm hook and the postgresql service is still running after remounting the device in the proper location for the storage subordinate to handle it. We still will exit(1) to alert the juju admin to the manual juju deploy & add-relation steps needed for ongoing volume support. But, in this new approach, the service still has integrity, and a resolved --retry will succeed (as well as the following config_changed hook).

> After the operator has performed the tasks given by step 5, we have a PostgreS...

Read more...

Revision history for this message
Stuart Bishop (stub) wrote :
Download full text (9.8 KiB)

This is all looking good. The branch as it stands is an improvement and can be landed. I've got comments below which you might want to act on before I land this. Please let me know when you are happy with the branch and want it landed, or if you want further review.

Thanks :)

=== modified file 'README.md'

+## Upgrade-charm hook notes

Yay documentation.

=== modified file 'config.yaml'

+ storage_mount_point:
+ default: "/mnt/storage"
+ type: string
+ description: |
+ The location where the storage is going to be mounted.

Do we have a use case for this to be configurable? If not, consider removing it. We already have a huge number of knobs in the PG charm, and this extra one might make further work on the storage subordinate harder.

If storage_mount_point remains, what happens if someone changes it? You will want to add the item to unchangable_config in validate_config().

-def config_changed_volume_apply():
+def config_changed_volume_apply(mount_point=None):

Its probably better to not have the default of None here.

+ if not mount_point:
+ log("Invalid volume storage configuration, not applying changes",
             ERROR)
+ return False

You abort if mount_point is false here...

+ if not mount_point:
+ log(
+ "invalid mount point = {}, "
+ "not applying changes.".format(mount_point), ERROR)
+ return False

... which means this code will never be reached and should be removed.

+ if ((os.path.islink(data_directory_path) and
+ os.readlink(data_directory_path) == new_pg_version_cluster_dir and
+ os.path.isdir(new_pg_version_cluster_dir))):
+ log(
+ "postgresql data dir '{}' already points "
+ "to {}, skipping storage changes.".format(
+ data_directory_path, new_pg_version_cluster_dir))
+ log(
+ "existing-symlink: to fix/avoid UID changes from "
+ "previous units, doing: "
+ "chown -R postgres:postgres {}".format(new_pg_dir))
+ run("chown -R postgres:postgres %s" % new_pg_dir)
+ return True

When do you see invalid file ownerships btw? Is this masking some other problem that needs to be addressed?

+ # Create a directory structure below "new" mount_point, as e.g.:
+ # config["storage_mount_point"]/postgresql/9.1/main , which "mimics":
+ # /var/lib/postgresql/9.1/main
+ curr_dir_stat = os.stat(data_directory_path)
+ for new_dir in [new_pg_dir,
+ os.path.join(new_pg_dir, version),
+ new_pg_version_cluster_dir]:
+ if not os.path.isdir(new_dir):
+ log("mkdir %s".format(new_dir))
+ os.mkdir(new_dir)
+ # copy permissions from current data_directory_path
+ os.chown(new_dir, curr_dir_stat.st_uid, curr_dir_stat.st_gid)
+ os.chmod(new_dir, curr_dir_stat.st_mode)

Rather than this loop, you can use charmhelpers.core.host.mkdir here. You can set the directory perms to 0700 rather than copy the existing ones.

+ # Carefully build this symlink, e.g.:
+ # /var/lib/postgresql/9.1/main ->
+ # config["storage_mount_point"]/postgresql...

review: Approve
Revision history for this message
Chad Smith (chad.smith) wrote :

Stuart, thanks for the final review here:

- I have dropped the storage_mount_point config option as there is no use case we can think of here. The mountpoint can be a global defined within the charm itself external_volume_mount.

- config_changed_volume_apply now takes a mandatory mount_point parameter and the unreached mount_point check is removed

- When do you see invalid file ownerships btw? Is this masking some other problem that needs to be addressed?

+ run("chown -R postgres:postgres %s" % new_pg_dir)
> When do you see invalid file ownerships btw? Is this masking some other problem that needs to be addressed?

I believe we saw this when the subdirectories on the external volume were created by postgresql/0 and we ended up spinning up destroying the unit and attaching the new postgresql/1 later to the previously configured volume. So, if there were a different uid/gid on the new installation for the postgresql user, we could have a mismatch right?

I had also seen that root user (via the charm hooks) created the directories and symlink with root:root and we needed to chown as postgres:postgres to ensure the Pg service could actually read and traverse the directories.

- Now we are using host.mkdir w/ postgres user and perms 700

We'll push on charmers this next week to get both storage subordinate and block-storage-broker charms into the charm store.

We are good for a merge up now just ran through a deployment to ensure the changes didn't break anything.

review: Approve
Revision history for this message
Stuart Bishop (stub) wrote :

On 1 March 2014 06:38, Chad Smith <email address hidden> wrote:

> - When do you see invalid file ownerships btw? Is this masking some other problem that needs to be addressed?
>
> + run("chown -R postgres:postgres %s" % new_pg_dir)
>> When do you see invalid file ownerships btw? Is this masking some other problem that needs to be addressed?
>
> I believe we saw this when the subdirectories on the external volume were created by postgresql/0 and we ended up spinning up destroying the unit and attaching the new postgresql/1 later to the previously configured volume. So, if there were a different uid/gid on the new installation for the postgresql user, we could have a mismatch right?

This makes sense, and will happen normally with some disaster recovery
strategies (bringing up an existing database on a new node).

> We'll push on charmers this next week to get both storage subordinate and block-storage-broker charms into the charm store.
>
> We are good for a merge up now just ran through a deployment to ensure the changes didn't break anything.
> --
> https://code.launchpad.net/~chad.smith/charms/precise/postgresql/postgresql-using-storage-subordinate/+merge/206078
> You are reviewing the proposed merge of lp:~chad.smith/charms/precise/postgresql/postgresql-using-storage-subordinate into lp:charms/postgresql.

I don't think we can land this until after the storage subordinate and
block-storage-broker charms have landed in the charm store, as they
are required to migrate from earlier revisions of the PostgreSQL
charm.

--
Stuart Bishop <<email address hidden>

Revision history for this message
Chad Smith (chad.smith) wrote :

> I don't think we can land this until after the storage subordinate and
> block-storage-broker charms have landed in the charm store, as they
> are required to migrate from earlier revisions of the PostgreSQL
> charm.
>
>
> --
> Stuart Bishop <<email address hidden>

Sounds good Stuart, we'll get Landscape team to finish the EC2 reviews for storage and block-storage-broker tomorrow or wednesday. Then we'll hitup charmers on those 2 new charms and I'll ping you when they are both in the charmstore

Revision history for this message
Matt Bruzek (mbruzek) wrote :

Moving this merge request to "Work In Progress" until the other charms land. Once those charms land move the propoal back to "Needs review" and that will add it back to the review queue.

Revision history for this message
Chad Smith (chad.smith) wrote :

Thank you Matt for the "WIP" flag here while we awaited charm release. I have now unblocked this charm as both storage charms are both added to the charm store.

storage subordinate:
  https://jujucharms.com/sidebar/search/precise/storage-0/?text=storage

block-storage-broker:
  https://jujucharms.com/sidebar/search/precise/block-storage-broker-2/?text=block-storage-broker

Revision history for this message
Stuart Bishop (stub) wrote :

I've gone through this again, and think it is good to land. I had several concerns, all of which turned out to be false when I looked closer.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'README.md'
2--- README.md 2014-02-05 12:29:09 +0000
3+++ README.md 2014-02-28 23:23:59 +0000
4@@ -223,6 +223,33 @@
5 hooks.execute(sys.argv)
6
7
8+## Upgrade-charm hook notes
9+
10+The PostgreSQL charm has deprecated volume-map and volume-ephemeral-storage
11+configuration options in favor of using the storage subordinate charm for
12+general external storage management. If the installation being upgraded is
13+using these deprecated options, there are a couple of manual steps necessary
14+to finish migration and continue using the current external volumes.
15+Even though all data will remain intact, and PostgreSQL service will continue
16+running, the upgrade-charm hook will intentionally fail and exit 1 as well to
17+raise awareness of the manual procedure which will also be documented in the
18+juju logs on the PostgreSQL units.
19+
20+The following steps must be additionally performed to continue using external
21+volume maps for the PostgreSQL units once juju upgrade-charm is run from the
22+command line:
23+ 1. cat > storage.cfg <<EOF
24+ storage:
25+ provider:block-storage-broker
26+ root: /srv/data
27+ volume_map: "{postgresql/0: your-vol-id, postgresql/1: your-2nd-vol-id}"
28+ EOF
29+ 2. juju deploy --config storage.cfg storage
30+ 3. juju deploy block-storage-broker
31+ 4. juju add-relation block-storage-broker storage
32+ 5. juju resolved --retry postgresql/0 # for each postgresql unit running
33+ 6. juju add-relation postgresql storage
34+
35
36 # Contact Information
37
38
39=== modified file 'config.yaml'
40--- config.yaml 2014-01-23 11:41:03 +0000
41+++ config.yaml 2014-02-28 23:23:59 +0000
42@@ -282,36 +282,6 @@
43 default: 4.0
44 type: float
45 description: Random page cost
46- #------------------------------------------------------------------------
47- # Volume management
48- # volume-map, volume-dev_regexp are only used
49- # if volume-ephemeral-storage == False
50- #------------------------------------------------------------------------
51- volume-ephemeral-storage:
52- type: boolean
53- default: true
54- description: >
55- If false, a configure-error state will be raised if
56- volume-map[$JUJU_UNIT_NAME] is not set (see "volume-map"
57- below) - see "volume-map" below.
58- If true, service units won't try to use "volume-map" (and
59- related variables) to mount and use external (EBS) volumes,
60- thus storage lifetime will equal VM, thus ephemeral.
61- YOU'VE BEEN WARNED.
62- volume-map:
63- type: string
64- default: ""
65- description: >
66- YAML map as e.g. "{ postgres/0: vol-0000010, postgres/1: vol-0000016 }".
67- Service units will raise a "configure-error" condition if no volume-map
68- value is set for it - it's expected a human to set it properly to
69- resolve it.
70- volume-dev-regexp:
71- type: string
72- default: "/dev/vd[b-z]"
73- description: >
74- Block device for attached volumes as seen by the VM, will be "scanned"
75- for an unused device when "volume-map" is valid for the unit.
76 backup_dir:
77 default: "/var/lib/postgresql/backups"
78 type: string
79@@ -366,7 +336,6 @@
80 An advisory lock key used internally by the charm. You do not need
81 to change it unless it happens to conflict with an advisory lock key
82 being used by your applications.
83-
84 # Swift backups and PITR via SwiftWAL
85 swiftwal_container_prefix:
86 type: string
87
88=== added symlink 'hooks/data-relation-changed'
89=== target is u'hooks.py'
90=== added symlink 'hooks/data-relation-departed'
91=== target is u'hooks.py'
92=== added symlink 'hooks/data-relation-joined'
93=== target is u'hooks.py'
94=== modified file 'hooks/hooks.py'
95--- hooks/hooks.py 2014-02-11 09:11:32 +0000
96+++ hooks/hooks.py 2014-02-28 23:23:59 +0000
97@@ -15,8 +15,6 @@
98 import sys
99 from tempfile import NamedTemporaryFile
100 import time
101-import yaml
102-from yaml.constructor import ConstructorError
103
104 from charmhelpers import fetch
105 from charmhelpers.core import hookenv, host
106@@ -146,85 +144,8 @@
107 self.save()
108
109
110-###############################################################################
111-# Volume managment
112-###############################################################################
113-#------------------------------
114-# Get volume-id from juju config "volume-map" dictionary as
115-# volume-map[JUJU_UNIT_NAME]
116-# @return volid
117-#
118-#------------------------------
119-def volume_get_volid_from_volume_map():
120- volume_map = {}
121- try:
122- volume_map = yaml.load(hookenv.config('volume-map').strip())
123- if volume_map:
124- return volume_map.get(os.environ['JUJU_UNIT_NAME'])
125- except ConstructorError as e:
126- log("invalid YAML in 'volume-map': {}".format(e), WARNING)
127- return None
128-
129-
130-# Is this volume_id permanent ?
131-# @returns True if volid set and not --ephemeral, else:
132-# False
133-def volume_is_permanent(volid):
134- if volid and volid != "--ephemeral":
135- return True
136- return False
137-
138-
139-#------------------------------
140-# Returns a mount point from passed vol-id, e.g. /srv/juju/vol-000012345
141-#
142-# @param volid volume id (as e.g. EBS volid)
143-# @return mntpoint_path eg /srv/juju/vol-000012345
144-#------------------------------
145-def volume_mount_point_from_volid(volid):
146- if volid and volume_is_permanent(volid):
147- return "/srv/juju/%s" % volid
148- return None
149-
150-
151-# Do we have a valid storage state?
152-# @returns volid
153-# None config state is invalid - we should not serve
154-def volume_get_volume_id():
155- ephemeral_storage = hookenv.config('volume-ephemeral-storage')
156- volid = volume_get_volid_from_volume_map()
157- juju_unit_name = hookenv.local_unit()
158- if ephemeral_storage in [True, 'yes', 'Yes', 'true', 'True']:
159- if volid:
160- log(
161- "volume-ephemeral-storage is True, but " +
162- "volume-map[{!r}] -> {}".format(juju_unit_name, volid), ERROR)
163- return None
164- else:
165- return "--ephemeral"
166- else:
167- if not volid:
168- log(
169- "volume-ephemeral-storage is False, but "
170- "no volid found for volume-map[{!r}]".format(
171- hookenv.local_unit()), ERROR)
172- return None
173- return volid
174-
175-
176-# Initialize and/or mount permanent storage, it straightly calls
177-# shell helper
178-def volume_init_and_mount(volid):
179- command = ("scripts/volume-common.sh call " +
180- "volume_init_and_mount %s" % volid)
181- output = run(command)
182- if output.find("ERROR") >= 0:
183- return False
184- return True
185-
186-
187 def volume_get_all_mounted():
188- command = ("mount |egrep /srv/juju")
189+ command = ("mount |egrep %s" % external_volume_mount)
190 status, output = commands.getstatusoutput(command)
191 if status != 0:
192 return None
193@@ -834,109 +755,89 @@
194 # NOTE the only 2 "True" return points:
195 # 1) symlink already pointing to existing storage (no-op)
196 # 2) new storage properly initialized:
197-# - volume: initialized if not already (fdisk, mkfs),
198-# mounts it to e.g.: /srv/juju/vol-000012345
199 # - if fresh new storage dir: rsync existing data
200 # - manipulate /var/lib/postgresql/VERSION/CLUSTER symlink
201 #------------------------------------------------------------------------------
202-def config_changed_volume_apply():
203+def config_changed_volume_apply(mount_point):
204 version = pg_version()
205 cluster_name = hookenv.config('cluster_name')
206 data_directory_path = os.path.join(
207 postgresql_data_dir, version, cluster_name)
208
209 assert(data_directory_path)
210- volid = volume_get_volume_id()
211- if volid:
212- if volume_is_permanent(volid):
213- if not volume_init_and_mount(volid):
214- log(
215- "volume_init_and_mount failed, not applying changes",
216- ERROR)
217- return False
218-
219- if not os.path.exists(data_directory_path):
220- log(
221- "postgresql data dir {} not found, "
222- "not applying changes.".format(data_directory_path),
223- CRITICAL)
224- return False
225-
226- mount_point = volume_mount_point_from_volid(volid)
227- new_pg_dir = os.path.join(mount_point, "postgresql")
228- new_pg_version_cluster_dir = os.path.join(
229- new_pg_dir, version, cluster_name)
230- if not mount_point:
231- log(
232- "invalid mount point from volid = {}, "
233- "not applying changes.".format(mount_point), ERROR)
234- return False
235-
236- if ((os.path.islink(data_directory_path) and
237- os.readlink(data_directory_path) == new_pg_version_cluster_dir and
238- os.path.isdir(new_pg_version_cluster_dir))):
239- log(
240- "postgresql data dir '%s' already points "
241- "to {}, skipping storage changes.".format(
242- data_directory_path, new_pg_version_cluster_dir))
243- log(
244- "existing-symlink: to fix/avoid UID changes from "
245- "previous units, doing: "
246- "chown -R postgres:postgres {}".format(new_pg_dir))
247- run("chown -R postgres:postgres %s" % new_pg_dir)
248- return True
249-
250- # Create a directory structure below "new" mount_point, as e.g.:
251- # /srv/juju/vol-000012345/postgresql/9.1/main , which "mimics":
252- # /var/lib/postgresql/9.1/main
253- curr_dir_stat = os.stat(data_directory_path)
254- for new_dir in [new_pg_dir,
255- os.path.join(new_pg_dir, version),
256- new_pg_version_cluster_dir]:
257- if not os.path.isdir(new_dir):
258- log("mkdir %s".format(new_dir))
259- os.mkdir(new_dir)
260- # copy permissions from current data_directory_path
261- os.chown(new_dir, curr_dir_stat.st_uid, curr_dir_stat.st_gid)
262- os.chmod(new_dir, curr_dir_stat.st_mode)
263- # Carefully build this symlink, e.g.:
264- # /var/lib/postgresql/9.1/main ->
265- # /srv/juju/vol-000012345/postgresql/9.1/main
266- # but keep previous "main/" directory, by renaming it to
267- # main-$TIMESTAMP
268- if not postgresql_stop():
269- log("postgresql_stop() failed - can't migrate data.", ERROR)
270- return False
271- if not os.path.exists(os.path.join(
272- new_pg_version_cluster_dir, "PG_VERSION")):
273- log("migrating PG data {}/ -> {}/".format(
274- data_directory_path, new_pg_version_cluster_dir), WARNING)
275- # void copying PID file to perm storage (shouldn't be any...)
276- command = "rsync -a --exclude postmaster.pid {}/ {}/".format(
277- data_directory_path, new_pg_version_cluster_dir)
278- log("run: {}".format(command))
279- run(command)
280- try:
281- os.rename(data_directory_path, "{}-{}".format(
282- data_directory_path, int(time.time())))
283- log("NOTICE: symlinking {} -> {}".format(
284- new_pg_version_cluster_dir, data_directory_path))
285- os.symlink(new_pg_version_cluster_dir, data_directory_path)
286- log(
287- "after-symlink: to fix/avoid UID changes from "
288- "previous units, doing: "
289- "chown -R postgres:postgres {}".format(new_pg_dir))
290- run("chown -R postgres:postgres {}".format(new_pg_dir))
291- return True
292- except OSError:
293- log("failed to symlink {} -> {}".format(
294- data_directory_path, mount_point), CRITICAL)
295- return False
296- else:
297- log(
298- "Invalid volume storage configuration, not applying changes",
299- ERROR)
300- return False
301+
302+ if not os.path.exists(data_directory_path):
303+ log(
304+ "postgresql data dir {} not found, "
305+ "not applying changes.".format(data_directory_path),
306+ CRITICAL)
307+ return False
308+
309+ new_pg_dir = os.path.join(mount_point, "postgresql")
310+ new_pg_version_cluster_dir = os.path.join(
311+ new_pg_dir, version, cluster_name)
312+ if not mount_point:
313+ log(
314+ "invalid mount point = {}, "
315+ "not applying changes.".format(mount_point), ERROR)
316+ return False
317+
318+ if ((os.path.islink(data_directory_path) and
319+ os.readlink(data_directory_path) == new_pg_version_cluster_dir and
320+ os.path.isdir(new_pg_version_cluster_dir))):
321+ log(
322+ "postgresql data dir '{}' already points "
323+ "to {}, skipping storage changes.".format(
324+ data_directory_path, new_pg_version_cluster_dir))
325+ log(
326+ "existing-symlink: to fix/avoid UID changes from "
327+ "previous units, doing: "
328+ "chown -R postgres:postgres {}".format(new_pg_dir))
329+ run("chown -R postgres:postgres %s" % new_pg_dir)
330+ return True
331+
332+ # Create a directory structure below "new" mount_point as
333+ # external_volume_mount/postgresql/9.1/main
334+ for new_dir in [new_pg_dir,
335+ os.path.join(new_pg_dir, version),
336+ new_pg_version_cluster_dir]:
337+ if not os.path.isdir(new_dir):
338+ log("mkdir %s".format(new_dir))
339+ host.mkdir(new_dir, owner="postgres", perms=0o700)
340+ # Carefully build this symlink, e.g.:
341+ # /var/lib/postgresql/9.1/main ->
342+ # external_volume_mount/postgresql/9.1/main
343+ # but keep previous "main/" directory, by renaming it to
344+ # main-$TIMESTAMP
345+ if not postgresql_stop() and postgresql_is_running():
346+ log("postgresql_stop() failed - can't migrate data.", ERROR)
347+ return False
348+ if not os.path.exists(os.path.join(
349+ new_pg_version_cluster_dir, "PG_VERSION")):
350+ log("migrating PG data {}/ -> {}/".format(
351+ data_directory_path, new_pg_version_cluster_dir), WARNING)
352+ # void copying PID file to perm storage (shouldn't be any...)
353+ command = "rsync -a --exclude postmaster.pid {}/ {}/".format(
354+ data_directory_path, new_pg_version_cluster_dir)
355+ log("run: {}".format(command))
356+ run(command)
357+ try:
358+ os.rename(data_directory_path, "{}-{}".format(
359+ data_directory_path, int(time.time())))
360+ log("NOTICE: symlinking {} -> {}".format(
361+ new_pg_version_cluster_dir, data_directory_path))
362+ os.symlink(new_pg_version_cluster_dir, data_directory_path)
363+ run("chown -h postgres:postgres {}".format(data_directory_path))
364+ log(
365+ "after-symlink: to fix/avoid UID changes from "
366+ "previous units, doing: "
367+ "chown -R postgres:postgres {}".format(new_pg_dir))
368+ run("chown -R postgres:postgres {}".format(new_pg_dir))
369+ return True
370+ except OSError:
371+ log("failed to symlink {} -> {}".format(
372+ data_directory_path, mount_point), CRITICAL)
373+ return False
374
375
376 def token_sql_safe(value):
377@@ -947,30 +848,15 @@
378
379
380 @hooks.hook()
381-def config_changed(force_restart=False):
382+def config_changed(force_restart=False, mount_point=None):
383 validate_config()
384 config_data = hookenv.config()
385 update_repos_and_packages()
386
387- # Trigger volume initialization logic for permanent storage
388- volid = volume_get_volume_id()
389- if not volid:
390- ## Invalid configuration (whether ephemeral, or permanent)
391- postgresql_autostart(False)
392- postgresql_stop()
393- mounts = volume_get_all_mounted()
394- if mounts:
395- log("current mounted volumes: {}".format(mounts))
396- log(
397- "Disabled and stopped postgresql service, "
398- "because of broken volume configuration - check "
399- "'volume-ephemeral-storage' and 'volume-map'", ERROR)
400- sys.exit(1)
401-
402- if volume_is_permanent(volid):
403- ## config_changed_volume_apply will stop the service if it founds
404+ if mount_point is not None:
405+ ## config_changed_volume_apply will stop the service if it finds
406 ## it necessary, ie: new volume setup
407- if config_changed_volume_apply():
408+ if config_changed_volume_apply(mount_point=mount_point):
409 postgresql_autostart(True)
410 else:
411 postgresql_autostart(False)
412@@ -1084,8 +970,89 @@
413
414 @hooks.hook()
415 def upgrade_charm():
416+ """Handle saving state during an upgrade-charm hook.
417+
418+ When upgrading from an installation using volume-map, we migrate
419+ that installation to use the storage subordinate charm by remounting
420+ a mountpath that the storage subordinate maintains. We exit(1) only to
421+ raise visibility to manual procedure that we log in juju logs below for the
422+ juju admin to finish the migration by relating postgresql to the storage
423+ and block-storage-broker services. These steps are generalised in the
424+ README as well.
425+ """
426 install(run_pre=False)
427 snapshot_relations()
428+ version = pg_version()
429+ cluster_name = hookenv.config('cluster_name')
430+ data_directory_path = os.path.join(
431+ postgresql_data_dir, version, cluster_name)
432+ if (os.path.islink(data_directory_path)):
433+ link_target = os.readlink(data_directory_path)
434+ if "/srv/juju" in link_target:
435+ # Then we just upgraded from an installation that was using
436+ # charm config volume_map definitions. We need to stop postgresql
437+ # and remount the device where the storage subordinate expects to
438+ # control the mount in the future if relations/units change
439+ volume_id = link_target.split("/")[3]
440+ unit_name = hookenv.local_unit()
441+ new_mount_root = external_volume_mount
442+ new_pg_version_cluster_dir = os.path.join(
443+ new_mount_root, "postgresql", version, cluster_name)
444+ if not os.exists(new_mount_root):
445+ os.mkdir(new_mount_root)
446+ log("\n"
447+ "WARNING: %s unit has external volume id %s mounted via the\n"
448+ "deprecated volume-map and volume-ephemeral-storage\n"
449+ "configuration parameters.\n"
450+ "These parameters are no longer available in the postgresql\n"
451+ "charm in favor of using the volume_map parameter in the\n"
452+ "storage subordinate charm.\n"
453+ "We are migrating the attached volume to a mount path which\n"
454+ "can be managed by the storage subordinate charm. To\n"
455+ "continue using this volume_id with the storage subordinate\n"
456+ "follow this procedure.\n-----------------------------------\n"
457+ "1. cat > storage.cfg <<EOF\nstorage:\n"
458+ " provider: block-storage-broker\n"
459+ " root: %s\n"
460+ " volume_map: \"{%s: %s}\"\nEOF\n2. juju deploy "
461+ "--config storage.cfg storage\n"
462+ "3. juju deploy block-storage-broker\n4. juju add-relation "
463+ "block-storage-broker storage\n5. juju resolved --retry "
464+ "%s\n6. juju add-relation postgresql storage\n"
465+ "-----------------------------------\n" %
466+ (unit_name, volume_id, new_mount_root, unit_name, volume_id,
467+ unit_name), WARNING)
468+ postgresql_stop()
469+ os.unlink(data_directory_path)
470+ log("Unmounting external storage due to charm upgrade: %s" %
471+ link_target)
472+ try:
473+ subprocess.check_output(
474+ "umount /srv/juju/%s" % volume_id, shell=True)
475+ # Since e2label truncates labels to 16 characters use only the
476+ # first 16 characters of the volume_id as that's what was
477+ # set by old versions of postgresql charm
478+ subprocess.check_call(
479+ "mount -t ext4 LABEL=%s %s" %
480+ (volume_id[:16], new_mount_root), shell=True)
481+ except subprocess.CalledProcessError, e:
482+ log("upgrade-charm mount migration failed. %s" % str(e), ERROR)
483+ sys.exit(1)
484+
485+ log("NOTICE: symlinking {} -> {}".format(
486+ new_pg_version_cluster_dir, data_directory_path))
487+ os.symlink(new_pg_version_cluster_dir, data_directory_path)
488+ run("chown -h postgres:postgres {}".format(data_directory_path))
489+ postgresql_start() # Will exit(1) if issues
490+ log("Remount and restart success for this external volume.\n"
491+ "This current running installation will break upon\n"
492+ "add/remove postgresql units or relations if you do not\n"
493+ "follow the above procedure to ensure your external\n"
494+ "volumes are preserved by the storage subordinate charm.",
495+ WARNING)
496+ # So juju admins can see the hook fail and note the steps to fix
497+ # per our WARNINGs above
498+ sys.exit(1)
499
500
501 @hooks.hook()
502@@ -2194,6 +2161,32 @@
503 host.service_reload('nagios-nrpe-server')
504
505
506+@hooks.hook('data-relation-changed')
507+def data_relation_changed():
508+ """Listen for configured mountpoint from storage subordinate relation"""
509+ if not hookenv.relation_get("mountpoint"):
510+ hookenv.log("Waiting for mountpoint from the relation: %s"
511+ % external_volume_mount, hookenv.DEBUG)
512+ else:
513+ hookenv.log("Storage ready and mounted", hookenv.DEBUG)
514+ config_changed(mount_point=external_volume_mount)
515+
516+
517+@hooks.hook('data-relation-joined')
518+def data_relation_joined():
519+ """Request mountpoint from storage subordinate by setting mountpoint"""
520+ hookenv.log("Setting mount point in the relation: %s"
521+ % external_volume_mount, hookenv.DEBUG)
522+ hookenv.relation_set(mountpoint=external_volume_mount)
523+
524+
525+@hooks.hook('data-relation-departed')
526+def stop_postgres_on_data_relation_departed():
527+ hookenv.log("Data relation departing. Stopping PostgreSQL",
528+ hookenv.DEBUG)
529+ postgresql_stop()
530+
531+
532 def _get_postgresql_config_dir(config_data=None):
533 """ Return the directory path of the postgresql configuration files. """
534 if config_data is None:
535@@ -2202,6 +2195,7 @@
536 cluster_name = config_data['cluster_name']
537 return os.path.join("/etc/postgresql", version, cluster_name)
538
539+
540 ###############################################################################
541 # Global variables
542 ###############################################################################
543@@ -2216,6 +2210,7 @@
544 local_state = State('local_state.pickle')
545 hook_name = os.path.basename(sys.argv[0])
546 juju_log_dir = "/var/log/juju"
547+external_volume_mount = "/srv/data"
548
549
550 if __name__ == '__main__':
551
552=== modified file 'hooks/test_hooks.py'
553--- hooks/test_hooks.py 2014-01-08 08:55:50 +0000
554+++ hooks/test_hooks.py 2014-02-28 23:23:59 +0000
555@@ -43,9 +43,23 @@
556 certain data is set.
557 """
558
559- _relation_data = {}
560+ _incoming_relation_data = ()
561+ _outgoing_relation_data = ()
562 _relation_ids = {}
563 _relation_list = ("postgres/0",)
564+ _log = ()
565+
566+ _log_DEBUG = ()
567+ _log_INFO = ()
568+ _log_WARNING = ()
569+ _log_ERROR = ()
570+ _log_CRITICAL = ()
571+
572+ DEBUG = "DEBUG"
573+ INFO = "INFO"
574+ WARNING = "WARNING"
575+ ERROR = "ERROR"
576+ CRITICAL = "CRITICAL"
577
578 def __init__(self):
579 self._config = {
580@@ -95,27 +109,30 @@
581 "wal_buffers": "-1",
582 "checkpoint_segments": 3,
583 "random_page_cost": 4.0,
584- "volume_ephemeral_storage": True,
585 "volume_map": "",
586 "volume_dev_regexp": "/dev/db[b-z]",
587 "backup_dir": "/var/lib/postgresql/backups",
588 "backup_schedule": "13 4 * * *",
589 "backup_retention_count": 7,
590 "nagios_context": "juju",
591+ "pgdg": False,
592+ "install_sources": "",
593+ "install_keys": "",
594 "extra_archives": "",
595 "advisory_lock_restart_key": 765}
596
597 def relation_set(self, *args, **kwargs):
598 """
599- Capture result of relation_set into _relation_data, which
600+ Capture result of relation_set into _outgoing_relation_data, which
601 can then be checked later.
602 """
603 if "relation_id" in kwargs:
604 del kwargs["relation_id"]
605- self._relation_data = dict(self._relation_data, **kwargs)
606+
607 for arg in args:
608 (key, value) = arg.split("=")
609- self._relation_data[key] = value
610+ self._outgoing_relation_data = (
611+ self._outgoing_relation_data + ((key, value),))
612
613 def relation_ids(self, relation_name="db-admin"):
614 """
615@@ -156,17 +173,24 @@
616 def juju_log(self, *args, **kwargs):
617 pass
618
619- def log(self, *args, **kwargs):
620- pass
621+ def log(self, message, level=None):
622+ if level is None:
623+ level = self.INFO
624+ log = getattr(self, "_log_%s" % level)
625+ setattr(self, "_log_%s" % level, log + (message,))
626
627- def config_get(self, scope=None):
628+ def config(self, scope=None):
629 if scope is None:
630- return self.config
631+ return self._config
632 else:
633- return self.config[scope]
634+ return self._config[scope]
635
636 def relation_get(self, scope=None, unit_name=None, relation_id=None):
637- pass
638+ if scope:
639+ for (key, value) in self._incoming_relation_data:
640+ if key == scope:
641+ return value
642+ return None
643
644
645 class TestHooks(mocker.MockerTestCase):
646@@ -175,8 +199,6 @@
647 hooks.hookenv = TestJuju()
648 hooks.host = TestJujuHost()
649 hooks.juju_log_dir = self.makeDir()
650- hooks.hookenv.config = lambda: hooks.hookenv._config
651- #hooks.hookenv.localunit = lambda: "localhost"
652 hooks.os.environ["JUJU_UNIT_NAME"] = "landscape/1"
653 hooks.os.environ["CHARM_DIR"] = os.path.abspath(
654 os.path.join(os.path.dirname(__file__), os.pardir))
655@@ -210,6 +232,65 @@
656
657 class TestHooksService(TestHooks):
658
659+ def test_data_relation_departed_stops_postgresql(self):
660+ """
661+ When the storage subordinate charm relation departs firing the
662+ C{data-relation-departed} hook, the charm stops the postgresql service
663+ and logs a message.
664+ """
665+ postgresql_stop = self.mocker.replace(hooks.postgresql_stop)
666+ postgresql_stop()
667+ self.mocker.replay()
668+ hooks.stop_postgres_on_data_relation_departed()
669+ message = "Data relation departing. Stopping PostgreSQL"
670+ self.assertIn(
671+ message, hooks.hookenv._log_DEBUG, "Not logged- %s" % message)
672+
673+ def test_data_relation_joined_requests_configured_mountpoint(self):
674+ """
675+ When postgresql is related to the storage subordinate charm via the
676+ 'data' relation it will read the configured C{storage_mount_point} and
677+ set C{mountpoint} in the relation in order to request a specific
678+ mountpoint from the storage charm.
679+ """
680+ mount = hooks.external_volume_mount
681+ hooks.data_relation_joined()
682+ message = "Setting mount point in the relation: %s" % mount
683+ self.assertIn(
684+ message, hooks.hookenv._log_DEBUG, "Not logged- %s" % message)
685+
686+ def test_data_relation_changed_waits_for_data_relation_mountpoint(self):
687+ """
688+ C{data_relation_changed} will wait for the storage charm to respond
689+ with the properly configured C{mountpoint} in the 'data' relation
690+ before calling C{config_changed}.
691+ """
692+ mount = hooks.external_volume_mount
693+ hooks.hookenv._config["storage_mount_point"] = mount
694+ self.assertEqual(hooks.hookenv._incoming_relation_data, ())
695+ hooks.data_relation_changed()
696+ message = "Waiting for mountpoint from the relation: %s" % mount
697+ self.assertIn(
698+ message, hooks.hookenv._log_DEBUG, "Not logged- %s" % message)
699+
700+ def test_data_relation_changed_mountpoint_present(self):
701+ """
702+ C{data_relation_changed} will call C{config_changed} when it receives
703+ the successfuly mounted C{mountpoint} from storage charm.
704+ """
705+ mount = hooks.external_volume_mount
706+ self.addCleanup(
707+ setattr, hooks.hookenv, "_incoming_relation_data", ())
708+ hooks.hookenv._incoming_relation_data = (("mountpoint", mount),)
709+ config_changed = self.mocker.replace(hooks.config_changed)
710+ config_changed(mount_point=mount)
711+ self.mocker.replay()
712+
713+ hooks.data_relation_changed()
714+ message = "Storage ready and mounted"
715+ self.assertIn(
716+ message, hooks.hookenv._log_DEBUG, "Not logged- %s" % message)
717+
718 def test_create_postgresql_config_wal_no_replication(self):
719 """
720 When postgresql is in C{standalone} mode, and participates in no
721
722=== modified file 'metadata.yaml'
723--- metadata.yaml 2013-10-08 22:44:28 +0000
724+++ metadata.yaml 2014-02-28 23:23:59 +0000
725@@ -23,6 +23,10 @@
726 nrpe-external-master:
727 interface: nrpe-external-master
728 scope: container
729+ data:
730+ interface: block-storage
731+ scope: container
732+ optional: true
733 requires:
734 persistent-storage:
735 interface: directory-path
736
737=== removed directory 'scripts'
738=== removed file 'scripts/volume-common.sh'
739--- scripts/volume-common.sh 2012-10-23 09:44:43 +0000
740+++ scripts/volume-common.sh 1970-01-01 00:00:00 +0000
741@@ -1,220 +0,0 @@
742-#!/bin/bash
743-# Author: JuanJo Ciarlante <jjo@canonical.com>
744-# Copyright: Canonical Ltd. 2012
745-# License: GPLv2
746-#
747-# juju storage common shell library
748-#
749-
750-#------------------------------
751-# Returns a mount point from passed vol-id, e.g. /srv/juju/vol-000012345
752-#
753-# @param $1 volume id
754-# @echoes mntpoint-path eg /srv/juju/vol-000012345
755-#------------------------------
756-_mntpoint_from_volid() {
757- local volid=${1?missing volid}
758- [[ ${volid} != "" ]] && echo /srv/juju/${volid} || echo ""
759-}
760-
761-
762-#------------------------------
763-# Assert that passed mount points hold different filesystems
764-#
765-# @param $1 mntpoint1
766-# @param $2 mntpoint2
767-# @return 0 different FS
768-# 1 same FS
769-#------------------------------
770-_assert_diff_fs() {
771- local mnt1="${1:?missing mntpoint1}"
772- local mnt2="${2:?missing mntpoint2}"
773- local fsid1 fsid2
774- fsid1=$(stat --file-system -c '%i' "${mnt1}" 2>/dev/null)
775- fsid2=$(stat --file-system -c '%i' "${mnt2}" 2>/dev/null)
776- [[ ${fsid1} != ${fsid2} ]]
777- return $?
778-}
779-
780-#------------------------------
781-# Initialize volume (sfdisk, mkfs.ext4) IFF NOT already, mount it at
782-# /srv/juju/<volume-id>
783-#
784-# @param $1 volume-id, can be any arbitrary string, better if
785-# equal to EC2/OS vol-id name (just for consistency)
786-# @return 0 success
787-# 1 nil volid/etc
788-# 2 error while handling the device (non-block device, sfdisk error, etc)
789-#------------------------------
790-volume_init_and_mount() {
791- ## Find 1st unused device (reverse sort /dev/vdX)
792- local volid=${1:?missing volid}
793- local dev_regexp
794- local dev found_dev=
795- local label="${volid}"
796- local func=${FUNCNAME[0]}
797- dev_regexp=$(config-get volume-dev-regexp) || return 1
798- mntpoint=$(_mntpoint_from_volid ${volid})
799-
800- [[ -z ${mntpoint} ]] && return 1
801- if mount | egrep -qw "${mntpoint}";then
802- _assert_diff_fs "/" "${mntpoint}" || {
803- juju-log "ERROR: returning from ${func} with '${mntpoint}' still at '/' filesystem"
804- return 1
805- }
806- juju-log "NOTICE: mntpoint=${mntpoint} already mounted, skipping volume_init_and_mount"
807- return 0
808- fi
809-
810- # Sanitize
811- case "${dev_regexp?}" in
812- # Careful: this is glob matching against an regexp -
813- # quite narrowed
814- /dev/*|/dev/disk/by-*)
815- ;; ## Ok
816- *)
817- juju-log "ERROR: invalid 'volume-dev-regexp' specified"
818- return 1
819- ;;
820- esac
821-
822- # Assume udev will create only existing devices
823- for dev in $(ls -rd1 /dev/* | egrep "${dev_regexp}" | egrep -v "[1-9]$" 2>/dev/null);do
824- ## Check it's not already mounted
825- mount | egrep -q "${dev}[1-9]?" || { found_dev=${dev}; break;}
826- done
827- [[ -n "${found_dev}" ]] || {
828- juju-log "ERROR: ${func}: coult not find an unused device for regexp: ${dev_regexp}"
829- return 1
830- }
831- partition1_dev=${found_dev}1
832-
833- juju-log "INFO: ${func}: found_dev=${found_dev}"
834- [[ -b ${found_dev?} ]] || {
835- juju-log "ERROR: ${func}: ${found_dev} is not a blockdevice"
836- return 2
837- }
838-
839- # Run next set of "dangerous" commands as 'set -e', in a subshell
840- (
841- set -e
842- # Re-read partition - will fail if already in use
843- blockdev --rereadpt ${found_dev}
844-
845- # IFF not present, create partition with full disk
846- if [[ -b ${partition1_dev?} ]];then
847- juju-log "INFO: ${func}: ${partition1_dev} already present - skipping sfdisk."
848- else
849- juju-log "NOTICE: ${func}: ${partition1_dev} not present at ${found_dev}, running: sfdisk ${found_dev} ..."
850- # Format partition1_dev as max sized
851- echo ",+," | sfdisk ${found_dev}
852- fi
853-
854- # Create an ext4 filesystem if NOT already present
855- # use e.g. LABEl=vol-000012345
856- if file -s ${partition1_dev} | egrep -q ext4 ; then
857- juju-log "INFO: ${func}: ${partition1_dev} already formatted as ext4 - skipping mkfs.ext4."
858- ## Check e2label - log if it has changed (e.g. already used / initialized with a diff label)
859- local curr_label=$(e2label "${partition1_dev}")
860- if [[ ${curr_label} != ${label} ]]; then
861- juju-log "WARNING: ${func}: ${partition1_dev} had label=${curr_label}, overwritting with label=${label}"
862- e2label ${partition1_dev} "${label}"
863- fi
864- else
865- juju-log "NOTICE: ${func}: running: mkfs.ext4 -L ${label} ${partition1_dev}"
866- mkfs.ext4 -L "${label}" ${partition1_dev}
867- fi
868-
869- # Mount it at e.g. /srv/juju/vol-000012345
870- [[ -d "${mntpoint}" ]] || mkdir -p "${mntpoint}"
871- mount | fgrep -wq "${partition1_dev}" || {
872- local files_below_mntpoint="$(ls -d "${mntpoint}"/* 2>/dev/null |wc -l )"
873- if [[ ${files_below_mntpoint} -ne 0 ]]; then
874- juju-log "ERROR: *not* doing 'mount "${partition1_dev}" "${mntpoint}"' because there are already ${files_below_mntpoint} files/dirs beneath '${mntpoint}'"
875- exit 1
876- fi
877- ## should always fsck before mounting (e.g. fsck after max time (-i) / max mounts (-c) )
878- fsck "${partition1_dev}"
879- mount "${partition1_dev}" "${mntpoint}"
880- juju-log "INFO: ${func}: mounted as: '$(mount | fgrep -w ${partition1_dev})'"
881- }
882-
883- # Add it to fstab is not already there
884- fgrep -wq "LABEL=${label}" /etc/fstab || {
885- echo "LABEL=${label} ${mntpoint} ext4 defaults,nobootwait,comment=${volid}" | tee -a /etc/fstab
886- juju-log "INFO: ${func}: LABEL=${label} added to /etc/fstab"
887- }
888- )
889- # Final assertion: mounted filesystem id is different from '/' (effectively mounted)
890- _assert_diff_fs "/" "${mntpoint}" || {
891- juju-log "ERROR: returning from ${func} with '${mntpoint}' still at '/' filesystem (couldn't mount new volume)"
892- ## try to rmdir mntpoint directory - should not be 'mistakenly' used
893- rmdir ${mntpoint}
894- return 1
895- }
896- return $?
897-}
898-
899-#------------------------------
900-# Get volume-id from juju config "volume-map" dictionary as
901-# volume-map[JUJU_UNIT_NAME]
902-# @return 0 if volume-map value found ( does echo volid or ""), else:
903-# 1 if not found or None
904-#
905-#------------------------------
906-volume_get_volid_from_volume_map() {
907- local volid=$(config-get "volume-map"|python -c$'import sys;import os;from yaml import load;from itertools import chain; volume_map = load(sys.stdin)\nif volume_map: print volume_map.get(os.environ["JUJU_UNIT_NAME"])')
908- [[ $volid == None ]] && return 1
909- echo "$volid"
910-}
911-
912-# Returns true if permanent storage (considers --ephemeral)
913-# @returns 0 if volid set and not --ephemeral, else:
914-# 1
915-volume_is_permanent() {
916- local volid=${1:?missing volid}
917- [[ -n ${volid} && ${volid} != --ephemeral ]] && return 0 || return 1
918-}
919-volume_mount_point_from_volid(){
920- local volid=${1:?missing volid}
921- if volume_is_permanent;then
922- echo "/srv/juju/${volid}"
923- return 0
924- else
925- return 1
926- fi
927-}
928-# Do we have a valid storage state?
929-# @returns 0 does echo $volid (can be "--ephemeral")
930-# 1 config state is invalid - we should not serve
931-volume_get_volume_id() {
932- local ephemeral_storage
933- local volid
934- ephemeral_storage=$(config-get volume-ephemeral-storage) || return 1
935- volid=$(volume_get_volid_from_volume_map) || return 1
936- if [[ $ephemeral_storage == True ]];then
937- # Ephemeral -> should not have a valid volid
938- if [[ $volid != "" ]];then
939- juju-log "ERROR: volume-ephemeral-storage is True, but $JUJU_UNIT_NAME maps to volid=${volid}"
940- return 1
941- fi
942- else
943- # Durable (not ephemeral) -> must have a valid volid for this unit
944- if [[ $volid == "" ]];then
945- juju-log "ERROR: volume-ephemeral-storage is False, but no volid found for: $JUJU_UNIT_NAME"
946- return 1
947- fi
948- fi
949- echo "$volid"
950- return 0
951-}
952-
953-case "$1" in
954- ## allow non SHELL scripts to call helper functions
955- call)
956- : ${JUJU_UNIT_NAME?} ## Must be called in juju environment
957- shift;
958- function="${1:?usage: ${0##*/} call function arg1 arg2 ...}"
959- shift;
960- ${function} "$@" && exit 0 || exit 1
961-esac

Subscribers

People subscribed via source and target branches