From a1638d19936e68132292eec0bd848f39520e03db Mon Sep 17 00:00:00 2001 From: Adam Gandelman Date: Wed, 12 Dec 2012 09:18:54 -0800 Subject: [PATCH 01/13] Initial pass at Swift rewrite. --- config.yaml | 41 ++- hooks/config-changed | 1 + hooks/identity-service-relation-changed | 1 + hooks/identity-service-relation-joined | 1 + hooks/install | 2 +- hooks/lib/__init__.py | 0 hooks/lib/openstack_common.py | 206 +++++++++++ hooks/object-store-relation-changed | 1 - hooks/object-store-relation-joined | 1 - hooks/start | 1 - hooks/stop | 1 - hooks/swift-hooks.py | 167 +++++++++ ...gin-s3_1.0.0~git201200618-0ubuntu1_all.deb | Bin 8320 -> 0 bytes hooks/swift-proxy-common | 215 ----------- hooks/swift-proxy-relation-broken | 2 +- hooks/swift-proxy-relation-changed | 2 +- hooks/swift-proxy-relation-joined | 1 - hooks/swift-proxy-relations | 167 --------- hooks/swift_utils.py | 348 ++++++++++++++++++ hooks/test.py | 4 + hooks/utils.py | 237 ++++++++++++ metadata.yaml | 3 +- revision | 2 +- 23 files changed, 1008 insertions(+), 396 deletions(-) create mode 120000 hooks/config-changed create mode 120000 hooks/identity-service-relation-changed create mode 120000 hooks/identity-service-relation-joined create mode 100644 hooks/lib/__init__.py create mode 100644 hooks/lib/openstack_common.py delete mode 120000 hooks/object-store-relation-changed delete mode 120000 hooks/object-store-relation-joined delete mode 120000 hooks/start delete mode 120000 hooks/stop create mode 100755 hooks/swift-hooks.py delete mode 100644 hooks/swift-plugin-s3_1.0.0~git201200618-0ubuntu1_all.deb delete mode 100755 hooks/swift-proxy-common delete mode 120000 hooks/swift-proxy-relation-joined delete mode 100755 hooks/swift-proxy-relations create mode 100644 hooks/swift_utils.py create mode 100644 hooks/test.py create mode 100644 hooks/utils.py diff --git a/config.yaml b/config.yaml index 894762f..93c1459 100644 --- a/config.yaml +++ b/config.yaml @@ -1,8 +1,25 @@ options: - swift-release: + openstack-origin: default: distro type: string - description: Swift PPA to configure (trunk, milestone, distro) + description: | + Repository from which to install. May be one of the following: + distro (default), ppa:somecustom/ppa, a deb url sources entry, + or a supported Cloud Archive release pocket. + . + Supported Cloud Archive sources include: + - cloud:precise-folsom, + - cloud:precise-folsom/updates + - cloud:precise-folsom/staging + - cloud:precise-folsom/proposed + . + Note that updating this setting to a source that is known to + provide a later version of OpenStack will trigger a software + upgrade. + region: + default: RegionOne + type: string + description: OpenStack region that this swift-proxy supports. # Ring configuration partition-power: default: 8 @@ -16,10 +33,21 @@ options: default: 1 type: int description: Minimum hours between balances + storage-zone-distribution: + default: "service-unit" + type: string + description: | + Storage zone distribution policy that the charm will use when + configuring and initializing the storage ring upon new swift-storage + relations (see README). Options include: + . + service-unit - Storage zones configured per swift-storage service unit. + machine-unit - Storage zones configured per swift-storage machine-unit. + manual - Storage zones configured manually per swift-storage service. # CA Cert info use-https: - default: 1 - type: int + default: "yes" + type: string description: Whether to listen on HTTPS country: default: US @@ -45,10 +73,15 @@ options: default: 0 type: int description: Number of TCP workers to launch (0 for the number of system cores) + operator-roles: + default: "Member,Admin" + type: string + description: Comma-separated list of Swift operator roles. auth-type: default: tempauth type: string description: Auth method to use, tempauth or keystone + # Manual Keystone configuration. keystone-auth-host: type: string description: Keystone authentication host diff --git a/hooks/config-changed b/hooks/config-changed new file mode 120000 index 0000000..fee03fe --- /dev/null +++ b/hooks/config-changed @@ -0,0 +1 @@ +swift-hooks.py \ No newline at end of file diff --git a/hooks/identity-service-relation-changed b/hooks/identity-service-relation-changed new file mode 120000 index 0000000..fee03fe --- /dev/null +++ b/hooks/identity-service-relation-changed @@ -0,0 +1 @@ +swift-hooks.py \ No newline at end of file diff --git a/hooks/identity-service-relation-joined b/hooks/identity-service-relation-joined new file mode 120000 index 0000000..fee03fe --- /dev/null +++ b/hooks/identity-service-relation-joined @@ -0,0 +1 @@ +swift-hooks.py \ No newline at end of file diff --git a/hooks/install b/hooks/install index c5c507b..fee03fe 120000 --- a/hooks/install +++ b/hooks/install @@ -1 +1 @@ -swift-proxy-relations \ No newline at end of file +swift-hooks.py \ No newline at end of file diff --git a/hooks/lib/__init__.py b/hooks/lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hooks/lib/openstack_common.py b/hooks/lib/openstack_common.py new file mode 100644 index 0000000..97733a8 --- /dev/null +++ b/hooks/lib/openstack_common.py @@ -0,0 +1,206 @@ +#!/usr/bin/python + +# Common python helper functions used for OpenStack charms. + +import subprocess + +CLOUD_ARCHIVE_URL = "http://ubuntu-cloud.archive.canonical.com/ubuntu" +CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA' + +ubuntu_openstack_release = { + 'oneiric': 'diablo', + 'precise': 'essex', + 'quantal': 'folsom', + 'raring' : 'grizzly' +} + + +openstack_codenames = { + '2011.2': 'diablo', + '2012.1': 'essex', + '2012.2': 'folsom', + '2013.1': 'grizzly' +} + +# The ugly duckling +swift_codenames = { + '1.4.3': 'diablo', + '1.4.8': 'essex', + '1.7.4': 'folsom' +} + +def juju_log(msg): + print msg + return + subprocess.check_call(['juju-log', msg]) + + +def error_out(msg): + juju_log("FATAL ERROR: %s" % msg) + exit(1) + + +def lsb_release(): + '''Return /etc/lsb-release in a dict''' + lsb = open('/etc/lsb-release', 'r') + d = {} + for l in lsb: + k, v = l.split('=') + d[k.strip()] = v.strip() + return d + + +def get_os_codename_install_source(src): + '''Derive OpenStack release codename from a given installation source.''' + ubuntu_rel = lsb_release()['DISTRIB_CODENAME'] + + rel = '' + if src == 'distro': + try: + rel = ubuntu_openstack_release[ubuntu_rel] + except KeyError: + e = 'Code not derive openstack release for '\ + 'this Ubuntu release: %s' % rel + error_out(e) + return rel + + if src.startswith('cloud:'): + ca_rel = src.split(':')[1] + ca_rel = ca_rel.split('%s-' % ubuntu_rel)[1].split('/')[0] + return ca_rel + + # Best guess match based on deb string provided + if src.startswith('deb'): + for k, v in openstack_codenames.iteritems(): + if v in src: + return v + +def get_os_codename_version(vers): + '''Determine OpenStack codename from version number.''' + try: + return openstack_codenames[vers] + except KeyError: + e = 'Could not determine OpenStack codename for version %s' % vers + error_out(e) + + +def get_os_version_codename(codename): + '''Determine OpenStack version number from codename.''' + for k, v in openstack_codenames.iteritems(): + if v == codename: + return k + e = 'Code not derive OpenStack version for '\ + 'codename: %s' % codename + error_out(e) + + +def get_os_codename_package(pkg): + '''Derive OpenStack release codename from an installed package.''' + cmd = ['dpkg', '-l', pkg] + + try: + output = subprocess.check_output(cmd) + except subprocess.CalledProcessError: + e = 'Could not derive OpenStack version from package that is not '\ + 'installed; %s' % pkg + error_out(e) + + def _clean(line): + line = line.split(' ') + clean = [] + for c in line: + if c != '': + clean.append(c) + return clean + + vers = None + for l in output.split('\n'): + if l.startswith('ii'): + l = _clean(l) + if l[1] == pkg: + vers = l[2] + + if not vers: + e = 'Could not determine version of installed package: %s' % pkg + error_out(e) + + vers = vers[:6] + try: + if 'swift' in pkg: + vers = vers[:5] + return swift_codenames[vers] + else: + vers = vers[:6] + return openstack_codenames[vers] + except KeyError: + e = 'Could not determine OpenStack codename for version %s' % vers + error_out(e) + + +def configure_installation_source(rel): + '''Configure apt installation source.''' + + def _import_key(id): + cmd = "apt-key adv --keyserver keyserver.ubuntu.com " \ + "--recv-keys %s" % id + try: + subprocess.check_call(cmd.split(' ')) + except: + error_out("Error importing repo key %s" % id) + + if rel == 'distro': + return + elif rel[:4] == "ppa:": + src = rel + subprocess.check_call(["add-apt-repository", "-y", src]) + elif rel[:3] == "deb": + l = len(rel.split('|')) + if l == 2: + src, key = rel.split('|') + juju_log("Importing PPA key from keyserver for %s" % src) + _import_key(key) + elif l == 1: + src = rel + else: + error_out("Invalid openstack-release: %s" % rel) + + with open('/etc/apt/sources.list.d/juju_deb.list', 'w') as f: + f.write(src) + elif rel[:6] == 'cloud:': + ubuntu_rel = lsb_release()['DISTRIB_CODENAME'] + rel = rel.split(':')[1] + u_rel = rel.split('-')[0] + ca_rel = rel.split('-')[1] + + if u_rel != ubuntu_rel: + e = 'Cannot install from Cloud Archive pocket %s on this Ubuntu '\ + 'version (%s)' % (ca_rel, ubuntu_rel) + error_out(e) + + if ca_rel == 'folsom/staging': + # staging is just a regular PPA. + cmd = 'add-apt-repository -y ppa:ubuntu-cloud-archive/folsom-staging' + subprocess.check_call(cmd.split(' ')) + return + + # map charm config options to actual archive pockets. + pockets = { + 'folsom': 'precise-updates/folsom', + 'folsom/updates': 'precise-updates/folsom', + 'folsom/proposed': 'precise-proposed/folsom' + } + + try: + pocket = pockets[ca_rel] + except KeyError: + e = 'Invalid Cloud Archive release specified: %s' % rel + error_out(e) + + src = "deb %s %s main" % (CLOUD_ARCHIVE_URL, pocket) + _import_key(CLOUD_ARCHIVE_KEY_ID) + + with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as f: + f.write(src) + else: + error_out("Invalid openstack-release specified: %s" % rel) + diff --git a/hooks/object-store-relation-changed b/hooks/object-store-relation-changed deleted file mode 120000 index c5c507b..0000000 --- a/hooks/object-store-relation-changed +++ /dev/null @@ -1 +0,0 @@ -swift-proxy-relations \ No newline at end of file diff --git a/hooks/object-store-relation-joined b/hooks/object-store-relation-joined deleted file mode 120000 index c5c507b..0000000 --- a/hooks/object-store-relation-joined +++ /dev/null @@ -1 +0,0 @@ -swift-proxy-relations \ No newline at end of file diff --git a/hooks/start b/hooks/start deleted file mode 120000 index c5c507b..0000000 --- a/hooks/start +++ /dev/null @@ -1 +0,0 @@ -swift-proxy-relations \ No newline at end of file diff --git a/hooks/stop b/hooks/stop deleted file mode 120000 index c5c507b..0000000 --- a/hooks/stop +++ /dev/null @@ -1 +0,0 @@ -swift-proxy-relations \ No newline at end of file diff --git a/hooks/swift-hooks.py b/hooks/swift-hooks.py new file mode 100755 index 0000000..d3aa4e1 --- /dev/null +++ b/hooks/swift-hooks.py @@ -0,0 +1,167 @@ +#!/usr/bin/python + +import os +import utils +import sys +import shutil +import uuid +from subprocess import check_call + +import lib.openstack_common as openstack +import swift_utils as swift + +def install(): + src = utils.config_get('openstack-origin') + if src != 'distro': + openstack.configure_installation_source(src) + check_call(['apt-get', 'update']) + rel = openstack.get_os_codename_install_source(src) + + pkgs = swift.determine_packages(rel) + utils.install(*pkgs) + + uid, gid = swift.swift_user() + conf_dir = os.path.dirname(swift.SWIFT_CONF) + if not os.path.isdir(conf_dir): + os.mkdir(conf_dir, 0750) + os.chown(conf_dir, uid, gid) + + swift.ensure_swift_dir() + + # initialize swift configs. + # swift.conf hash + ctxt = { + 'swift_hash': swift.get_swift_hash() + } + with open(swift.SWIFT_CONF, 'w') as conf: + conf.write(swift.render_config(swift.SWIFT_CONF, ctxt)) + + # swift-proxy.conf + swift.write_proxy_config() + + # memcached.conf + ctxt = { 'proxy_ip': utils.get_host_ip() } + with open(swift.MEMCACHED_CONF, 'w') as conf: + conf.write(swift.render_config(swift.MEMCACHED_CONF, ctxt)) + + # generate or setup SSL certificate + swift.configure_ssl() + + # initialize new storage rings. + for ring in swift.SWIFT_RINGS.iteritems(): + swift.initialize_ring(ring[1], + utils.config_get('partition-power'), + utils.config_get('replicas'), + utils.config_get('min-hours')) + + # configure a directory on webserver for distributing rings. + if not os.path.isdir(swift.WWW_DIR): + os.mkdir(swift.WWW_DIR, 0755) + os.chown(swift.WWW_DIR, uid, gid) + swift.write_apache_config() + + +def keystone_joined(relid=None): + hostname = utils.unit_get('private-address') + port = utils.config_get('bind-port') + ssl = utils.config_get('use-https') + if ssl == 'yes': + proto = 'https' + else: + proto = 'http' + admin_url = '%s://%s:%s' % (proto, hostname, port) + internal_url = public_url = '%s/v1/AUTH_$(tenant_id)s' % admin_url + utils.relation_set(service='swift', + region=utils.config_get('region'), + public_url=public_url, internal_url=internal_url, + admin_url=admin_url, + requested_roles=utils.config_get('operator-roles'), + rid=relid) + + +def keystone_changed(): + swift.write_proxy_config() + + +def balance_rings(): + '''handle doing ring balancing and distribution.''' + new_ring = False + for ring in swift.SWIFT_RINGS.itervalues(): + if swift.balance_ring(ring): + utils.juju_log('INFO', 'Balanced ring %s' % ring) + new_ring = True + if not new_ring: + return + + for ring in swift.SWIFT_RINGS.keys(): + f = '%s.ring.gz' % ring + shutil.copyfile(os.path.join(swift.SWIFT_CONF_DIR, f), + os.path.join(swift.WWW_DIR, f)) + + msg = 'Broadcasting notification to all storage nodes that new '\ + 'ring is ready for consumption.' + utils.juju_log('INFO', msg) + + www_dir = swift.WWW_DIR.split('/var/www/')[1] + trigger = uuid.uuid4() + swift_hash = swift.get_swift_hash() + # notify storage nodes that there is a new ring to fetch. + for relid in utils.relation_ids('swift-proxy'): + utils.relation_set(rid=relid, swift_hash=swift_hash, + www_dir=www_dir, trigger=trigger) + swift.proxy_control('restart') + +def proxy_changed(): + account_port = utils.config_get('account-ring-port') + object_port = utils.config_get('object-ring-port') + container_port = utils.config_get('container-ring-port') + node_settings = { + 'ip': utils.get_host_ip(utils.relation_get('private-address')), + 'zone': utils.relation_get('zone'), + 'account_port': utils.relation_get('account_port'), + 'object_port': utils.relation_get('object_port'), + 'container_port': utils.relation_get('container_port'), + } + if None in node_settings.itervalues(): + utils.juju_log('INFO', 'proxy_changed: Relation not ready.') + return None + + for k in ['zone', 'account_port', 'object_port', 'container_port']: + node_settings[k] = int(node_settings[k]) + + # Grant new node access to rings via apache. + swift.write_apache_config() + + # allow for multiple devs per unit, passed along as a : separated list + devs = utils.relation_get('device').split(':') + for dev in devs: + node_settings['device'] = dev + for ring in swift.SWIFT_RINGS.itervalues(): + if not swift.exists_in_ring(ring, node_settings): + swift.add_to_ring(ring, node_settings) + + if swift.should_balance([r for r in swift.SWIFT_RINGS.itervalues()]): + balance_rings() + +def proxy_broken(): + swift.write_apache_config() + +def config_changed(): + relids = utils.relation_ids('identity-service') + if relids: + for relid in relids: + keystone_joined(relid) + swift.write_proxy_config() + +hooks = { + 'install': install, + 'config-changed': config_changed, + 'identity-service-relation-joined': keystone_joined, + 'identity-service-relation-changed': keystone_changed, + 'swift-proxy-relation-changed': proxy_changed, + 'swift-proxy-relation-broken': proxy_broken, +} + +utils.do_hooks(hooks) + +sys.exit(0) diff --git a/hooks/swift-plugin-s3_1.0.0~git201200618-0ubuntu1_all.deb b/hooks/swift-plugin-s3_1.0.0~git201200618-0ubuntu1_all.deb deleted file mode 100644 index df479ee6d62c4345809625cc8154b77442426753..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8320 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 WBg_A9iE9f9@z57BhMRzc!}wnaR8b@V diff --git a/hooks/swift-proxy-common b/hooks/swift-proxy-common deleted file mode 100755 index 66ee488..0000000 --- a/hooks/swift-proxy-common +++ /dev/null @@ -1,215 +0,0 @@ -#!/bin/bash -set -u -# For openssl cert generation -USE_HTTPS=$(config-get use-https) -COUNTRY=$(config-get country) -STATE=$(config-get state) -LOCALE=$(config-get locale) -COMMON_NAME=$(config-get common-name) -PPA=$(config-get swift-release) -BINDPORT=$(config-get bind-port) -WORKERS=$(config-get workers) -AUTHTYPE=$(config-get auth-type) -KEYSTONE_AUTH_HOST=$(config-get keystone-auth-host) -KEYSTONE_AUTH_PORT=$(config-get keystone-auth-port) -KEYSTONE_AUTH_PROTOCOL=$(config-get keystone-auth-protocol) -KEYSTONE_ADMIN_TENANT_NAME=$(config-get keystone-admin-tenant-name) -KEYSTONE_ADMIN_USER=$(config-get keystone-admin-user) -KEYSTONE_ADMIN_PASSWORD=$(config-get keystone-admin-password) - -# Used in proxy-server.conf. Run one worker per cpu core by default. -CORES=$(cat /proc/cpuinfo | grep processor | wc -l) -[ "$WORKERS" = "0" ] && WORKERS="$CORES" - -# TODO: Need to use different addresses for internal swift traffic -# as this the only security measure in place is network isolation -PROXY_LOCAL_NET_IP=`dig +short $(unit-get private-address)` - -# Use apache2 to distribute ring config until there is support -# for file xfer in juju -PACKAGES="swift swift-proxy memcached apache2" -if [ "$AUTHTYPE" = "keystone" ]; then - PACKAGES="$PACKAGES python-keystone" -fi -WWW_DIR="/var/www/swift-rings" -SWIFT_HASH_FILE="/var/lib/juju/swift-hash-path.conf" - -# Ring configuration -PARTITION_POWER=$(config-get partition-power) -REPLICAS=$(config-get replicas) -MIN_HOURS=$(config-get min-hours) - -# generate the swift hash to be used for salting URLs of objects. -# TODO: its important this is never lost, find out some way of getting -# it off the server and into a sys admins INBOX? -if [[ ! -e $SWIFT_HASH_FILE ]] ; then - juju-log "swift-proxy: Generating a new SWIFT_HASH in $SWIFT_HASH_FILE" - echo $(od -t x8 -N 8 -A n $SWIFT_HASH_FILE -fi - -function set_swift_hash { - # TODO: Do this with augeas and put in a utility function for use elsewhere - cat >/etc/swift/swift.conf </etc/swift/proxy-server.conf <>/etc/swift/proxy-server.conf <>/etc/swift/proxy-server.conf <>/etc/swift/proxy-server.conf <>/etc/swift/proxy-server.conf <>/etc/swift/proxy-server.conf </dev/null - if [[ $? == 0 ]] ; then - ZONE=$(cat $checked_in | grep $JUJU_REMOTE_UNIT | cut -d, -f2) - return 0 - fi - fi - if [[ ! -e $zone_file ]] ; then - echo 1 > $zone_file - fi - ZONE=$(cat $zone_file) - echo "$JUJU_REMOTE_UNIT,$ZONE" >>$checked_in - if [[ $ZONE == $REPLICAS ]] ; then - echo 1 >$zone_file - return 0 - fi - echo $[$ZONE+1] >$zone_file -} - -function add_to_ring { - juju-log "swift-proxy: Updating $1 ring. Adding $IP:$PORT, zone $ZONE, device $DEVICE" - swift-ring-builder /etc/swift/$1.builder add \ - z$ZONE-$IP:$PORT/$DEVICE 100 - rc=$? - if [[ "$rc" == "0" ]] ; then - juju-log "Added to ring: $IP:$PORT, zone $ZONE, device $DEVICE" - return 0 - fi - juju-log "swift-proxy: Failed to add to ring." - return 1 -} - -function exists_in_ring { - swift-ring-builder /etc/swift/$i.builder \ - search z$ZONE-$IP:$PORT/$DEVICE -} - -function rebalance_ring { - juju-log "Rebalancing ring $1" - swift-ring-builder /etc/swift/$i.builder rebalance - return $? -} - -function add_ppa { - # Don't configure PPA, install from archive. - [[ $PPA == "distro" ]] && return 0 - if [ "${PPA:0:4}" = "deb " ]; then - PPA_URL="$PPA" - else - . /etc/lsb-release - [[ $PPA == "milestone" ]] && PPA="release" - PPA_URL="deb http://ppa.launchpad.net/swift-core/$PPA/ubuntu $DISTRIB_CODENAME main" - fi - add-apt-repository "$PPA_URL" || exit 1 -} diff --git a/hooks/swift-proxy-relation-broken b/hooks/swift-proxy-relation-broken index c5c507b..fee03fe 120000 --- a/hooks/swift-proxy-relation-broken +++ b/hooks/swift-proxy-relation-broken @@ -1 +1 @@ -swift-proxy-relations \ No newline at end of file +swift-hooks.py \ No newline at end of file diff --git a/hooks/swift-proxy-relation-changed b/hooks/swift-proxy-relation-changed index c5c507b..fee03fe 120000 --- a/hooks/swift-proxy-relation-changed +++ b/hooks/swift-proxy-relation-changed @@ -1 +1 @@ -swift-proxy-relations \ No newline at end of file +swift-hooks.py \ No newline at end of file diff --git a/hooks/swift-proxy-relation-joined b/hooks/swift-proxy-relation-joined deleted file mode 120000 index c5c507b..0000000 --- a/hooks/swift-proxy-relation-joined +++ /dev/null @@ -1 +0,0 @@ -swift-proxy-relations \ No newline at end of file diff --git a/hooks/swift-proxy-relations b/hooks/swift-proxy-relations deleted file mode 100755 index a0f8586..0000000 --- a/hooks/swift-proxy-relations +++ /dev/null @@ -1,167 +0,0 @@ -#!/bin/bash -set -u - -FORMULA_DIR=$(dirname $0) -ARG0=${0##*/} - -if [[ -e $FORMULA_DIR/swift-proxy-common ]] ; then - . $FORMULA_DIR/swift-proxy-common -else - echo "ERROR: Could not load swift-proxy-common from $FORMULA_DIR" -fi - -function install_hook { - ### CANONICAL-SPECIFIC BEGIN ### - # MAAS preseed is forcing apt to proxy through the deb caching proxy - # on the MAAS server. This is preventing us from getting to - # cloud-archive. Stop it. - rm -f /etc/apt/apt.conf - ### CANONICAL-SPECIFIC END ### - - apt-get -y --force-yes install python-software-properties || exit 1 - add_ppa - apt-get update - for i in $PACKAGES ; do - DEBIAN_FRONTEND=noninteractive apt-get -y --force-yes install $i - done - - SWIFT_DEB_VERSION="$(dpkg-query -W -f='${Version}' 'swift-proxy')" - # We are shipping swift-plugin-s3 here until it becomes available in - # ubuntu-cloud.archive precise-updates/folsom - if [ "${SWIFT_DEB_VERSION:0:3}" = "1.7" ]; then - dpkg -i "$(dirname $0)/swift-plugin-s3_1.0.0~git201200618-0ubuntu1_all.deb" - fi - - mkdir -p /etc/swift - set_swift_hash || exit 1 - create_proxy_conf - mkdir $WWW_DIR - chown www-data:www-data $WWW_DIR - if [ "$USE_HTTPS" = "1" ]; then - if [[ ! -e /etc/swift/cert.crt ]] ; then - openssl req -new -x509 -nodes \ - -out /etc/swift/cert.crt \ - -keyout /etc/swift/cert.key \ - -subj "/C=$COUNTRY/ST=$STATE/L=$LOCALE/CN=$COMMON_NAME" - fi - fi - perl -pi -e "s/-l 127.0.0.1/-l $PROXY_LOCAL_NET_IP/" /etc/memcached.conf - service memcached restart - echo "swift-proxy-node - install: Initializing rings" - for i in account container object ; do initialize_ring $i ; done -} - -function proxy_joined { - exit 0 -} - -function proxy_changed { - HOST=`relation-get hostname` - DEVICES=`relation-get device` - get_zone - [[ -z $ZONE ]] || [[ -z $HOST ]] || [[ -z $DEVICES ]] && \ - echo "ZONE|HOST|DEVICES not set. Peer not ready? Exit 0 and wait." && exit 0 - - if [[ $ZONE -gt $REPLICAS ]] ; then - echo "ERROR: Peer $JUJU_REMOTE_UNIT attempting to join a non-existent zone!" - exit 1 - fi - PORT=6000 - RINGS="object container account" - IP=$(dig +short $HOST) - for i in $RINGS ; do - if [[ ! -e /etc/swift/$i.builder ]] ; then - echo "Ring $i missing, initializing" - initialize_ring $i - fi - done - - for i in $RINGS ; do - for DEVICE in $(echo $DEVICES | sed 's/:/ /g'); do - if ! exists_in_ring ; then - add_to_ring $i $ZONE $IP $PORT $DEVICE || exit 1 - else - juju-log "swift-proxy: $IP:$PORT/$DEVICE already exists in $ZONE" - fi - done - PORT=$[$PORT+1] - done - echo "Current peers:" - relation-list - current_peers=$(relation-list | wc -l) - - # checks to find out if we should rebalance rings - balance_file="/var/run/juju/swift-balanced" - - if [[ $current_peers -lt $REPLICAS ]] ; then - echo "Not enough peers to maitain minimum $REPLICAS replicas ($current_peers/$REPLICAS), skipping rebalance." - exit 0 - fi - - if [[ -e $balance_file ]] ; then - [[ $(cat $balance_file | cut -d, -f1) == $current_peers ]] && \ - echo "Ring already balanced since $current_peers present." - exit 0 - fi - - echo "Balancing rings" - for i in $RINGS ; do - rebalance_ring $i || exit 1 - done - - chown -R swift:swift /etc/swift - - stamp=`date +%Y%M%d-%H%M%S` - export_dir="$WWW_DIR/$stamp" - echo "$current_peers,$stamp" > $balance_file - - # rings have been balanced, push out new rings to nodes via webserver - mkdir $export_dir - echo "Copying rings to $export_dir for client consumption" - for i in $RINGS ; do - cp /etc/swift/$i.ring.gz $export_dir - done - - chown -R swift:swift /etc/swift - chown -R www-data $WWW_DIR - - relation-set update_url="http://$(unit-get private-address)/swift-rings/$stamp" - relation-set swift_hash=$(cat $SWIFT_HASH_FILE) - - swift-init proxy status || swift-init proxy start -} - -function proxy_broken { - # remove all ring configuration on broken - rm -rf /etc/swift/*.ring.gz - rm -rf /etc/swift/*.builder - rm -rf /etc/swift/backups - rm -rf /var/run/juju/swift-balanced - rm -rf /var/run/juju/checked-in - rm -rf /var/run/juju/swift-zone - rm -rf /var/www/swift-rings -} - -function object-store_joined { - # until we use keystone or another real auth system, - # just return a tempauth user from config. - USER=$(cat /etc/swift/proxy-server.conf | grep user_system_root | awk '{ print $1 }') - USER=${USER##*_} - PASSWORD=$(cat /etc/swift/proxy-server.conf | grep user_system_root | cut -d= -f2 | awk '{ print $1 }') - URL=https://$(unit-get private-address):8080/auth/v1.0 - relation-set user=$USER password=$PASSWORD url=$URL -} - -[[ -d /etc/swift ]] && chown -R swift /etc/swift - -juju-log "swift-proxy: Firing hook $ARG0" -case $ARG0 in - "install") install_hook ;; - "start"|"stop") exit 0 ;; - "swift-proxy-relation-joined") proxy_joined ;; - "swift-proxy-relation-changed") proxy_changed ;; - "swift-proxy-relation-broken") proxy_broken ;; - "object-store-relation-joined") object-store_joined ;; - "object-store-relation-changed") exit 0 ;; -esac - diff --git a/hooks/swift_utils.py b/hooks/swift_utils.py new file mode 100644 index 0000000..65cb0b4 --- /dev/null +++ b/hooks/swift_utils.py @@ -0,0 +1,348 @@ +import os +import pwd +import subprocess +import lib.openstack_common as openstack +import utils + +# Various config files that are managed via templating. +SWIFT_HASH_FILE='/var/lib/juju/swift-hash-path.conf' +SWIFT_CONF = '/etc/swift/swift.conf' +SWIFT_PROXY_CONF = '/etc/swift/proxy-server.conf' +SWIFT_CONF_DIR = os.path.dirname(SWIFT_CONF) +MEMCACHED_CONF = '/etc/memcached.conf' +APACHE_CONF = '/etc/apache2/conf.d/swift-rings' + +WWW_DIR = '/var/www/swift-rings' + +SWIFT_RINGS = { + 'account': '/etc/swift/account.builder', + 'container': '/etc/swift/container.builder', + 'object': '/etc/swift/object.builder' +} + +SSL_CERT = '/etc/swift/cert.crt' +SSL_KEY = '/etc/swift/cert.key' + +# Essex packages +BASE_PACKAGES = [ + 'swift', + 'swift-proxy', + 'memcached', + 'apache2', + 'python-keystone', +] + +# Folsom-specific packages +FOLSOM_PACKAGES = BASE_PACKAGES + ['swift-plugin-s3'] + +def proxy_control(action): + '''utility to work around swift-init's bad RCs.''' + def _cmd(action): + return ['swift-init', 'proxy-server', action] + + p = subprocess.Popen(_cmd('status'), stdout=subprocess.PIPE) + p.communicate() + status = p.returncode + if action == 'stop': + if status == 1: + return + elif status == 0: + return subprocess.check_call(_cmd('stop')) + + # the proxy will not start unless there are balanced rings, gzip'd in /etc/swift + missing=False + for k in SWIFT_RINGS.keys(): + if not os.path.exists(os.path.join(SWIFT_CONF_DIR, '%s.ring.gz' % k)): + missing = True + if missing: + utils.juju_log('INFO', 'Rings not balanced, skipping %s.' % action) + return + + if action == 'start': + if status == 0: + return + elif status == 1: + return subprocess.check_call(_cmd('start')) + elif action == 'restart': + if status == 0: + return subprocess.check_call(_cmd('restart')) + elif status == 1: + return subprocess.check_call(_cmd('start')) + +def swift_user(username='swift'): + user = pwd.getpwnam('swift') + return (user.pw_uid, user.pw_gid) + + +def ensure_swift_dir(conf_dir=os.path.dirname(SWIFT_CONF)): + if not os.path.isdir(conf_dir): + os.mkdir(conf_dir, 0750) + uid, gid = swift_user() + os.chown(conf_dir, uid, gid) + + +def determine_packages(release): + '''determine what packages are needed for a given OpenStack release''' + if release == 'essex': + return BASE_PACKAGES + elif release == 'folsom': + return FOLSOM_PACKAGES + + +def render_config(config_file, context): + '''write out config using templates for a specific openstack release.''' + os_release = openstack.get_os_codename_package('python-swift') + # load os release-specific templates. + cfile = os.path.basename(config_file) + templates_dir = os.path.join(utils.TEMPLATES_DIR, os_release) + return utils.render_template(cfile, context, templates_dir) + + +def get_swift_hash(): + if os.path.isfile(SWIFT_HASH_FILE): + with open(SWIFT_HASH_FILE, 'r') as hashfile: + swift_hash = hashfile.read().strip() + else: + cmd = ['od', '-t', 'x8', '-N', '8', '-A', 'n'] + rand = open('/dev/random', 'r') + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=rand) + swift_hash = p.communicate()[0].strip() + with open(SWIFT_HASH_FILE, 'w') as hashfile: + hashfile.write(swift_hash) + return swift_hash + + +def get_keystone_auth(): + '''return standard keystone auth credentials, either from config or the + identity-service relation. user-specified config is given priority + over an existing relation. + ''' + auth_type = utils.config_get('auth-type') + auth_host = utils.config_get('keystone-auth-host') + admin_user = utils.config_get('keystone-admin-user') + admin_password = utils.config_get('keystone-admin-user') + if (auth_type == 'keystone' and auth_host + and admin_user and admin_password): + utils.juju_log('INFO', 'Using user-specified Keystone configuration.') + ks_auth = { + 'auth_type': 'keystone', + 'auth_protocol': utils.config_get('keystone-auth-protocol'), + 'keystone_host': auth_host, + 'auth_port': utils.config_get('keystone-auth-port'), + 'service_user': admin_user, + 'service_password': admin_password, + 'service_tenant': utils.config_get('keystone-admin-tenant-name') + } + return ks_auth + + for relid in utils.relation_ids('identity-service'): + utils.juju_log('INFO', + 'Using Keystone configuration from identity-service.') + for unit in utils.relation_list(relid): + ks_auth = { + 'auth_type': 'keystone', + 'auth_protocol': 'http', + 'keystone_host': utils.relation_get('private-address', + unit, relid), + 'auth_port': utils.relation_get('auth_port', unit, relid), + 'service_user': utils.relation_get('service_username', unit, relid), + 'service_password': utils.relation_get('service_password', unit, relid), + 'service_tenant': utils.relation_get('service_tenant', unit, relid), + 'service_port': utils.relation_get('service_port', unit, relid), + 'admin_token': utils.relation_get('admin_token', unit, relid), + } + if None not in ks_auth.itervalues(): + return ks_auth + return None + + +def write_proxy_config(): + workers = utils.config_get('workers') + if workers == '0': + import multiprocessing + workers = multiprocessing.cpu_count() + + ctxt = { + 'proxy_ip': utils.get_host_ip(), + 'bind_port': utils.config_get('bind-port'), + 'workers': workers, + 'operator_roles': utils.config_get('operator-roles') + } + + if utils.config_get('use-https') == 'no': + ctxt['ssl'] = False + else: + ctxt['ssl'] = True + ctxt['ssl_cert'] = SSL_CERT + ctxt['ssl_key'] = SSL_KEY + + ks_auth = get_keystone_auth() + if ks_auth: + utils.juju_log('INFO', 'Enabling Keystone authentication.') + ctxt = (ctxt.items() + ks_auth.items()) + + with open(SWIFT_PROXY_CONF, 'w') as conf: + conf.write(render_config(SWIFT_PROXY_CONF, ctxt)) + + proxy_control('restart') + +def configure_ssl(): + # this should be expanded to cover setting up user-specified certificates + if (utils.config_get('use-https') == 'yes' and + not os.path.isfile(SSL_CERT) and + not os.path.isfile(SSL_KEY)): + subj = '/C=%s/ST=%s/L=%s/CN=%s' %\ + (utils.config_get('country'), utils.config_get('state'), + utils.config_get('locale'), utils.config_get('common-name')) + cmd = ['openssl', 'req', '-new', '-x509', '-nodes', + '-out', SSL_CERT, '-keyout', SSL_KEY, + '-subj', subj] + subprocess.check_call(cmd) + + +def _load_builder(path): + # lifted straight from /usr/bin/swift-ring-builder + from swift.common.ring import RingBuilder, Ring + import cPickle as pickle + try: + builder = pickle.load(open(path, 'rb')) + if not hasattr(builder, 'devs'): + builder_dict = builder + builder = RingBuilder(1, 1, 1) + builder.copy_from(builder_dict) + except ImportError: # Happens with really old builder pickles + modules['swift.ring_builder'] = \ + modules['swift.common.ring.builder'] + builder = RingBuilder(1, 1, 1) + builder.copy_from(pickle.load(open(argv[1], 'rb'))) + for dev in builder.devs: + if dev and 'meta' not in dev: + dev['meta'] = '' + return builder + + +def _write_ring(ring, ring_path): + import cPickle as pickle + pickle.dump(ring.to_dict(), open(ring_path, 'wb'), protocol=2) + + + + +def ring_port(ring_path, node): + '''determine correct port from relation settings for a given ring file.''' + for name in ['account', 'object', 'container']: + if name in ring_path: + return node[('%s_port' % name)] + + +def initialize_ring(path, part_power, replicas, min_hours): + '''Initialize a new swift ring with given parameters.''' + from swift.common.ring import RingBuilder + ring = RingBuilder(part_power, replicas, min_hours) + _write_ring(ring, path) + +def exists_in_ring(ring_path, node): + from swift.common.ring import RingBuilder, Ring + ring = _load_builder(ring_path).to_dict() + node['port'] = ring_port(ring_path, node) + + for dev in ring['devs']: + d = [(i, dev[i]) for i in dev if i in node] + n = [(i, node[i]) for i in node if i in dev] + if sorted(d) == sorted(n): + + msg = 'Node already exists in ring (%s).' % ring_path + utils.juju_log('INFO', msg) + return True + + return False + + +def add_to_ring(ring_path, node): + from swift.common.ring import RingBuilder, Ring + ring = _load_builder(ring_path) + port = ring_port(ring_path, node) + + devs = ring.to_dict()['devs'] + next_id = 0 + if devs: + next_id = len([d['id'] for d in devs]) + + new_dev = { + 'id': next_id, + 'zone': node['zone'], + 'ip': node['ip'], + 'port': port, + 'device': node['device'], + 'weight': 100, + 'meta': '', + } + ring.add_dev(new_dev) + _write_ring(ring, ring_path) + msg = 'Added new device to ring %s: %s' % (ring_path, + [k for k in new_dev.iteritems()]) + utils.juju_log('INFO', msg) + + +def determine_zone(policy): + '''Determine which storage zone a specific machine unit belongs to based + on configured storage-zone-distrbution policy.''' + if policy == 'service-unit': + this_relid = os.getenv('JUJU_RELATION_ID') + relids = utils.relation_ids('swift-proxy') + zone = (relids.index(this_relid) + 1) + elif policy == 'machine-unit': + pass + elif policy == 'manual': + zone = utils.relation_get('zone') + return zone + + +def balance_ring(ring_path): + '''balance a ring. return True if it needs redistribution''' + # shell out to swift-ring-builder instead, since the balancing code there + # does a bunch of un-importable validation.''' + cmd = ['swift-ring-builder', ring_path, 'rebalance'] + p = subprocess.Popen(cmd) + p.communicate() + rc = p.returncode + if rc == 0: + return True + elif rc == 1: + # swift-ring-builder returns 1 on WARNING (ring didn't require balance) + return False + else: + utils.juju_log('balance_ring: %s returned %s' % (cmd, rc)) + sys.exit(1) + +def should_balance(rings): + '''Based on zones vs min. replicas, determine whether or not the rings + should be balanaced during initial configuration.''' + do_rebalance = True + for ring in rings: + zones = [] + r = _load_builder(ring).to_dict() + replicas = r['replicas'] + zones = [d['zone'] for d in r['devs']] + if len(set(zones)) < replicas: + do_rebalance = False + return do_rebalance + + +def write_apache_config(): + '''write out /etc/apache2/conf.d/swift-rings with a list of authenticated + hosts''' + utils.juju_log('INFO', 'Updating %s.' % APACHE_CONF) + + allowed_hosts = [] + for relid in utils.relation_ids('swift-proxy'): + for unit in utils.relation_list(relid): + host = utils.relation_get('private-address', unit, relid) + allowed_hosts.append(host) + # testing + allowed_hosts.append('10.0.0.3') + ctxt = { 'www_dir': WWW_DIR, 'allowed_hosts': allowed_hosts } + with open(APACHE_CONF, 'w') as conf: + conf.write(render_config(APACHE_CONF, ctxt)) + subprocess.check_call(['service', 'apache2', 'reload']) + diff --git a/hooks/test.py b/hooks/test.py new file mode 100644 index 0000000..213c034 --- /dev/null +++ b/hooks/test.py @@ -0,0 +1,4 @@ +#!/usr/bin/python +import lib.openstack_common as openstack +pkg = 'swift-proxy' +print openstack.get_os_codename_package(pkg) diff --git a/hooks/utils.py b/hooks/utils.py new file mode 100644 index 0000000..29bd3e9 --- /dev/null +++ b/hooks/utils.py @@ -0,0 +1,237 @@ + +# +# Copyright 2012 Canonical Ltd. +# +# Authors: +# James Page +# Paul Collins +# + +import json +import os +import subprocess +import socket +import sys + + +def do_hooks(hooks): + hook = os.path.basename(sys.argv[0]) + + try: + hooks[hook]() + except KeyError: + juju_log('INFO', + "This charm doesn't know how to handle '{}'.".format(hook)) + + +def install(*pkgs): + cmd = [ + 'apt-get', + '-y', + 'install' + ] + for pkg in pkgs: + cmd.append(pkg) + subprocess.check_call(cmd) + +TEMPLATES_DIR = 'templates' + +try: + import jinja2 +except ImportError: + install('python-jinja2') + import jinja2 + +try: + import dns.resolver + import dns.ipv4 +except ImportError: + install('python-dnspython') + import dns.resolver + import dns.ipv4 + + +def render_template(template_name, context, template_dir=TEMPLATES_DIR): + templates = jinja2.Environment( + loader=jinja2.FileSystemLoader(template_dir) + ) + template = templates.get_template(template_name) + return template.render(context) + +CLOUD_ARCHIVE = \ +""" # Ubuntu Cloud Archive +deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main +""" + +CLOUD_ARCHIVE_POCKETS = { + 'folsom': 'precise-updates/folsom', + 'folsom/updates': 'precise-updates/folsom', + 'folsom/proposed': 'precise-proposed/folsom' + } + + +def configure_source(): + source = str(config_get('openstack-origin')) + if not source: + return + if source.startswith('ppa:'): + cmd = [ + 'add-apt-repository', + source + ] + subprocess.check_call(cmd) + if source.startswith('cloud:'): + install('ubuntu-cloud-keyring') + pocket = source.split(':')[1] + with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as apt: + apt.write(CLOUD_ARCHIVE.format(CLOUD_ARCHIVE_POCKETS[pocket])) + if source.startswith('deb'): + l = len(source.split('|')) + if l == 2: + (apt_line, key) = source.split('|') + cmd = [ + 'apt-key', + 'adv', '--keyserver keyserver.ubuntu.com', + '--recv-keys', key + ] + subprocess.check_call(cmd) + elif l == 1: + apt_line = source + + with open('/etc/apt/sources.list.d/quantum.list', 'w') as apt: + apt.write(apt_line + "\n") + cmd = [ + 'apt-get', + 'update' + ] + subprocess.check_call(cmd) + +# Protocols +TCP = 'TCP' +UDP = 'UDP' + + +def expose(port, protocol='TCP'): + cmd = [ + 'open-port', + '{}/{}'.format(port, protocol) + ] + subprocess.check_call(cmd) + + +def juju_log(severity, message): + cmd = [ + 'juju-log', + '--log-level', severity, + message + ] + subprocess.check_call(cmd) + + +def relation_ids(relation): + cmd = [ + 'relation-ids', + relation + ] + return subprocess.check_output(cmd).split() # IGNORE:E1103 + + +def relation_list(rid): + cmd = [ + 'relation-list', + '-r', rid, + ] + return subprocess.check_output(cmd).split() # IGNORE:E1103 + + +def relation_get(attribute, unit=None, rid=None): + cmd = [ + 'relation-get', + ] + if rid: + cmd.append('-r') + cmd.append(rid) + cmd.append(attribute) + if unit: + cmd.append(unit) + value = subprocess.check_output(cmd).strip() # IGNORE:E1103 + if value == "": + return None + else: + return value + + +def relation_set(**kwargs): + cmd = [ + 'relation-set' + ] + args = [] + for k, v in kwargs.items(): + if k == 'rid': + if v: + cmd.append('-r') + cmd.append(v) + else: + args.append('{}={}'.format(k, v)) + cmd += args + subprocess.check_call(cmd) + + +def unit_get(attribute): + cmd = [ + 'unit-get', + attribute + ] + value = subprocess.check_output(cmd).strip() # IGNORE:E1103 + if value == "": + return None + else: + return value + + +def config_get(attribute): + cmd = [ + 'config-get', + '--format', + 'json', + ] + out = subprocess.check_output(cmd).strip() # IGNORE:E1103 + cfg = json.loads(out) + + try: + return cfg[attribute] + except KeyError: + return None + +def get_unit_hostname(): + return socket.gethostname() + + +def get_host_ip(hostname=unit_get('private-address')): + try: + # Test to see if already an IPv4 address + socket.inet_aton(hostname) + return hostname + except socket.error: + try: + answers = dns.resolver.query(hostname, 'A') + if answers: + return answers[0].address + except dns.resolver.NXDOMAIN: + pass + return None + + +def restart(*services): + for service in services: + subprocess.check_call(['service', service, 'restart']) + + +def stop(*services): + for service in services: + subprocess.check_call(['service', service, 'stop']) + + +def start(*services): + for service in services: + subprocess.check_call(['service', service, 'start']) diff --git a/metadata.yaml b/metadata.yaml index 8fcbb8c..f1e3875 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -1,4 +1,3 @@ -ensemble: formula name: swift-proxy maintainer: Adam Gandelman summary: "Swift proxy node" @@ -13,3 +12,5 @@ provides: requires: swift-storage: interface: swift + identity-service: + interface: keystone diff --git a/revision b/revision index d15a2cc..d61f00d 100644 --- a/revision +++ b/revision @@ -1 +1 @@ -80 +90 From 80fccce28877bea37820f12b9e73e03d20aa8718 Mon Sep 17 00:00:00 2001 From: Adam Gandelman Date: Fri, 14 Dec 2012 15:07:01 -0800 Subject: [PATCH 02/13] Add templates, allow for automatic zone assignment. --- config.yaml | 18 ++++--- hooks/swift-hooks.py | 3 +- hooks/swift_utils.py | 69 ++++++++++++++++++------ hooks/templates/essex/memcached.conf | 47 ++++++++++++++++ hooks/templates/essex/proxy-server.conf | 64 ++++++++++++++++++++++ hooks/templates/essex/swift-rings | 7 +++ hooks/templates/essex/swift.conf | 4 ++ hooks/templates/folsom/memcached.conf | 1 + hooks/templates/folsom/proxy-server.conf | 1 + hooks/templates/folsom/swift-rings | 1 + hooks/templates/folsom/swift.conf | 1 + hooks/utils.py | 2 +- revision | 2 +- 13 files changed, 193 insertions(+), 27 deletions(-) create mode 100644 hooks/templates/essex/memcached.conf create mode 100644 hooks/templates/essex/proxy-server.conf create mode 100644 hooks/templates/essex/swift-rings create mode 100644 hooks/templates/essex/swift.conf create mode 120000 hooks/templates/folsom/memcached.conf create mode 120000 hooks/templates/folsom/proxy-server.conf create mode 120000 hooks/templates/folsom/swift-rings create mode 120000 hooks/templates/folsom/swift.conf diff --git a/config.yaml b/config.yaml index 93c1459..2e7d623 100644 --- a/config.yaml +++ b/config.yaml @@ -33,17 +33,19 @@ options: default: 1 type: int description: Minimum hours between balances - storage-zone-distribution: - default: "service-unit" + zone-assignment: + default: "manual" type: string description: | - Storage zone distribution policy that the charm will use when - configuring and initializing the storage ring upon new swift-storage - relations (see README). Options include: + Which policy to use when assigning new storage nodes to zones. . - service-unit - Storage zones configured per swift-storage service unit. - machine-unit - Storage zones configured per swift-storage machine-unit. - manual - Storage zones configured manually per swift-storage service. + manual - Allow swift-storage services to request zone membership. + auto - Assign new swift-storage units to zones automatically. + . + The configured replica minimum must be met by an equal number of storage + zones before the storage ring will be initially balance. Deployment + requirements differ based on the zone-assignment policy configured, see + this charm's README for details. # CA Cert info use-https: default: "yes" diff --git a/hooks/swift-hooks.py b/hooks/swift-hooks.py index d3aa4e1..aaff33b 100755 --- a/hooks/swift-hooks.py +++ b/hooks/swift-hooks.py @@ -115,9 +115,10 @@ def proxy_changed(): account_port = utils.config_get('account-ring-port') object_port = utils.config_get('object-ring-port') container_port = utils.config_get('container-ring-port') + zone = swift.get_zone(utils.config_get('zone-assignment')) node_settings = { 'ip': utils.get_host_ip(utils.relation_get('private-address')), - 'zone': utils.relation_get('zone'), + 'zone': zone, 'account_port': utils.relation_get('account_port'), 'object_port': utils.relation_get('object_port'), 'container_port': utils.relation_get('container_port'), diff --git a/hooks/swift_utils.py b/hooks/swift_utils.py index 65cb0b4..12e3f07 100644 --- a/hooks/swift_utils.py +++ b/hooks/swift_utils.py @@ -95,6 +95,7 @@ def render_config(config_file, context): # load os release-specific templates. cfile = os.path.basename(config_file) templates_dir = os.path.join(utils.TEMPLATES_DIR, os_release) + context['os_release'] = os_release return utils.render_template(cfile, context, templates_dir) @@ -157,6 +158,8 @@ def get_keystone_auth(): def write_proxy_config(): + + bind_port = utils.config_get('bind-port') workers = utils.config_get('workers') if workers == '0': import multiprocessing @@ -164,7 +167,7 @@ def write_proxy_config(): ctxt = { 'proxy_ip': utils.get_host_ip(), - 'bind_port': utils.config_get('bind-port'), + 'bind_port': bind_port, 'workers': workers, 'operator_roles': utils.config_get('operator-roles') } @@ -179,12 +182,14 @@ def write_proxy_config(): ks_auth = get_keystone_auth() if ks_auth: utils.juju_log('INFO', 'Enabling Keystone authentication.') - ctxt = (ctxt.items() + ks_auth.items()) + for k, v in ks_auth.iteritems(): + ctxt[k] = v with open(SWIFT_PROXY_CONF, 'w') as conf: conf.write(render_config(SWIFT_PROXY_CONF, ctxt)) proxy_control('restart') + subprocess.check_call(['open-port', bind_port]) def configure_ssl(): # this should be expanded to cover setting up user-specified certificates @@ -247,8 +252,8 @@ def exists_in_ring(ring_path, node): node['port'] = ring_port(ring_path, node) for dev in ring['devs']: - d = [(i, dev[i]) for i in dev if i in node] - n = [(i, node[i]) for i in node if i in dev] + d = [(i, dev[i]) for i in dev if i in node and i != 'zone'] + n = [(i, node[i]) for i in node if i in dev and i != 'zone'] if sorted(d) == sorted(n): msg = 'Node already exists in ring (%s).' % ring_path @@ -284,18 +289,50 @@ def add_to_ring(ring_path, node): utils.juju_log('INFO', msg) -def determine_zone(policy): - '''Determine which storage zone a specific machine unit belongs to based - on configured storage-zone-distrbution policy.''' - if policy == 'service-unit': - this_relid = os.getenv('JUJU_RELATION_ID') - relids = utils.relation_ids('swift-proxy') - zone = (relids.index(this_relid) + 1) - elif policy == 'machine-unit': - pass - elif policy == 'manual': - zone = utils.relation_get('zone') - return zone +def _get_zone(ring_builder): + replicas = ring_builder.replicas + zones = [d['zone'] for d in ring_builder.devs] + if not zones: + return 1 + if len(zones) < replicas: + return sorted(zones).pop() + 1 + + zone_distrib = {} + for z in zones: + zone_distrib[z] = zone_distrib.get(z, 0) + 1 + + if len(set([total for total in zone_distrib.itervalues()])) == 1: + # all zones are equal, start assigning to zone 1 again. + return 1 + + return sorted(zone_distrib, key=zone_distrib.get).pop(0) + + +def get_zone(assignment_policy): + ''' Determine the appropriate zone depending on configured assignment + policy. + + Manual assignment relies on each storage zone being deployed as a + separate service unit with its desired zone set as a configuration + option. + + Auto assignment distributes swift-storage machine units across a number + of zones equal to the configured minimum replicas. This allows for a + single swift-storage service unit, with each 'add-unit'd machine unit + being assigned to a different zone. + ''' + if assignment_policy == 'manual': + return utils.relation_get('zone') + elif assignment_policy == 'auto': + potential_zones = [] + for ring in SWIFT_RINGS.itervalues(): + builder = _load_builder(ring) + potential_zones.append(_get_zone(builder)) + return set(potential_zones).pop() + else: + utils.juju_log('Invalid zone assignment policy: %s' %\ + assignemnt_policy) + sys.exit(1) def balance_ring(ring_path): diff --git a/hooks/templates/essex/memcached.conf b/hooks/templates/essex/memcached.conf new file mode 100644 index 0000000..f8cff0b --- /dev/null +++ b/hooks/templates/essex/memcached.conf @@ -0,0 +1,47 @@ +# memcached default config file +# 2003 - Jay Bonci +# This configuration file is read by the start-memcached script provided as +# part of the Debian GNU/Linux distribution. + +# Run memcached as a daemon. This command is implied, and is not needed for the +# daemon to run. See the README.Debian that comes with this package for more +# information. +-d + +# Log memcached's output to /var/log/memcached +logfile /var/log/memcached.log + +# Be verbose +# -v + +# Be even more verbose (print client commands as well) +# -vv + +# Start with a cap of 64 megs of memory. It's reasonable, and the daemon default +# Note that the daemon will grow to this size, but does not start out holding this much +# memory +-m 64 + +# Default connection port is 11211 +-p 11211 + +# Run the daemon as root. The start-memcached will default to running as root if no +# -u command is present in this config file +-u memcache + +# Specify which IP address to listen on. The default is to listen on all IP addresses +# This parameter is one of the only security measures that memcached has, so make sure +# it's listening on a firewalled interface. +-l {{ proxy_ip }} + +# Limit the number of simultaneous incoming connections. The daemon default is 1024 +# -c 1024 + +# Lock down all paged memory. Consult with the README and homepage before you do this +# -k + +# Return error when memory is exhausted (rather than removing items) +# -M + +# Maximize core file limit +# -r diff --git a/hooks/templates/essex/proxy-server.conf b/hooks/templates/essex/proxy-server.conf new file mode 100644 index 0000000..edbc94c --- /dev/null +++ b/hooks/templates/essex/proxy-server.conf @@ -0,0 +1,64 @@ +[DEFAULT] +bind_port = {{ bind_port }} +workers = {{ workers }} +user = swift +{% if ssl %} +cert_file = {{ ssl_cert }} +key_file = {{ ssl_key }} +{% endif %} + +{% if auth_type == 'keystone' %} +[pipeline:main] +pipeline = healthcheck cache swift3 s3token authtoken keystone proxy-server +{% else %} +[pipeline:main] +pipeline = healthcheck cache tempauth proxy-server +{% endif %} + +[app:proxy-server] +use = egg:swift#proxy +allow_account_management = true +{% if auth_type == 'keystone' %}account_autocreate = true{% endif %} + +[filter:tempauth] +use = egg:swift#tempauth +user_system_root = testpass .admin https://{{ proxy_ip }}:8080/v1/AUTH_system + +[filter:healthcheck] +use = egg:swift#healthcheck + +[filter:cache] +use = egg:swift#memcache +memcache_servers = {{ proxy_ip }}:11211 + +{% if auth_type == 'keystone' %} +[filter:keystone] +paste.filter_factory = keystone.middleware.swift_auth:filter_factory +operator_roles = {{ operator_roles }} + +[filter:authtoken] +paste.filter_factory = keystone.middleware.auth_token:filter_factory +auth_host = {{ keystone_host }} +auth_port = {{ auth_port }} +auth_protocol = {{ auth_protocol }} +auth_uri = {{ auth_protocol }}://{{ keystone_host }}:{{ service_port }} +admin_tenant_name = {{ service_tenant }} +admin_user = {{ service_user }} +admin_password = {{ service_password }} +{% if os_release != 'essex' %}signing_dir = /etc/swift{% endif %} + + +[filter:s3token] +paste.filter_factory = keystone.middleware.s3_token:filter_factory +service_host = {{ keystone_host }} +service_port = {{ service_port }} +auth_port = {{ auth_port }} +auth_host = {{ keystone_host }} +auth_protocol = {{ auth_protocol }} +auth_token = {{ admin_token }} +admin_token = {{ admin_token }} + +[filter:swift3] +{% if os_release == 'essex' %}use = egg:swift#swift3{% else %}use = egg:swift3#swift3 +{% endif %} +{% endif %} diff --git a/hooks/templates/essex/swift-rings b/hooks/templates/essex/swift-rings new file mode 100644 index 0000000..5ea8390 --- /dev/null +++ b/hooks/templates/essex/swift-rings @@ -0,0 +1,7 @@ + + Order deny,allow +{% for host in allowed_hosts %} + Allow from {{ host }} +{% endfor %} + Deny from all + diff --git a/hooks/templates/essex/swift.conf b/hooks/templates/essex/swift.conf new file mode 100644 index 0000000..fe67d65 --- /dev/null +++ b/hooks/templates/essex/swift.conf @@ -0,0 +1,4 @@ +[swift-hash] +# random unique string that can never change (DO NOT LOSE) +swift_hash_path_suffix = {{ swift_hash }} + diff --git a/hooks/templates/folsom/memcached.conf b/hooks/templates/folsom/memcached.conf new file mode 120000 index 0000000..55feb6b --- /dev/null +++ b/hooks/templates/folsom/memcached.conf @@ -0,0 +1 @@ +../essex/memcached.conf \ No newline at end of file diff --git a/hooks/templates/folsom/proxy-server.conf b/hooks/templates/folsom/proxy-server.conf new file mode 120000 index 0000000..695f6f6 --- /dev/null +++ b/hooks/templates/folsom/proxy-server.conf @@ -0,0 +1 @@ +../essex/proxy-server.conf \ No newline at end of file diff --git a/hooks/templates/folsom/swift-rings b/hooks/templates/folsom/swift-rings new file mode 120000 index 0000000..4a86480 --- /dev/null +++ b/hooks/templates/folsom/swift-rings @@ -0,0 +1 @@ +../essex/swift-rings \ No newline at end of file diff --git a/hooks/templates/folsom/swift.conf b/hooks/templates/folsom/swift.conf new file mode 120000 index 0000000..9f252b6 --- /dev/null +++ b/hooks/templates/folsom/swift.conf @@ -0,0 +1 @@ +../essex/swift.conf \ No newline at end of file diff --git a/hooks/utils.py b/hooks/utils.py index 29bd3e9..283bbc6 100644 --- a/hooks/utils.py +++ b/hooks/utils.py @@ -34,7 +34,7 @@ def install(*pkgs): cmd.append(pkg) subprocess.check_call(cmd) -TEMPLATES_DIR = 'templates' +TEMPLATES_DIR = 'hooks/templates' try: import jinja2 diff --git a/revision b/revision index d61f00d..3ad5abd 100644 --- a/revision +++ b/revision @@ -1 +1 @@ -90 +99 From 3e3739c009d5bf032ae6bcb9b46097225ad909b5 Mon Sep 17 00:00:00 2001 From: Adam Gandelman Date: Fri, 14 Dec 2012 15:58:51 -0800 Subject: [PATCH 03/13] Fix open-port execution. --- hooks/swift_utils.py | 2 +- revision | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hooks/swift_utils.py b/hooks/swift_utils.py index 12e3f07..f37c7c3 100644 --- a/hooks/swift_utils.py +++ b/hooks/swift_utils.py @@ -189,7 +189,7 @@ def write_proxy_config(): conf.write(render_config(SWIFT_PROXY_CONF, ctxt)) proxy_control('restart') - subprocess.check_call(['open-port', bind_port]) + subprocess.check_call(['open-port', str(bind_port)]) def configure_ssl(): # this should be expanded to cover setting up user-specified certificates diff --git a/revision b/revision index 3ad5abd..29d6383 100644 --- a/revision +++ b/revision @@ -1 +1 @@ -99 +100 From 10122a1f1ce26f6be256985543ea2c662b75f85e Mon Sep 17 00:00:00 2001 From: Adam Gandelman Date: Fri, 14 Dec 2012 16:11:14 -0800 Subject: [PATCH 04/13] Use IPs instead of hostnames for Apache Allows. --- hooks/swift_utils.py | 5 ++--- revision | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/hooks/swift_utils.py b/hooks/swift_utils.py index f37c7c3..b7f9153 100644 --- a/hooks/swift_utils.py +++ b/hooks/swift_utils.py @@ -375,9 +375,8 @@ def write_apache_config(): for relid in utils.relation_ids('swift-proxy'): for unit in utils.relation_list(relid): host = utils.relation_get('private-address', unit, relid) - allowed_hosts.append(host) - # testing - allowed_hosts.append('10.0.0.3') + allowed_hosts.append(utils.get_host_ip(host)) + ctxt = { 'www_dir': WWW_DIR, 'allowed_hosts': allowed_hosts } with open(APACHE_CONF, 'w') as conf: conf.write(render_config(APACHE_CONF, ctxt)) diff --git a/revision b/revision index 29d6383..398050c 100644 --- a/revision +++ b/revision @@ -1 +1 @@ -100 +101 From bd95ce06cd5cce16796ab11cc49d7fbfb3280e70 Mon Sep 17 00:00:00 2001 From: Adam Gandelman Date: Fri, 14 Dec 2012 17:41:23 -0800 Subject: [PATCH 05/13] Add README. --- README | 132 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 README diff --git a/README b/README new file mode 100644 index 0000000..fe0d8e4 --- /dev/null +++ b/README @@ -0,0 +1,132 @@ +Overview +-------- + +This charm provides the swift-proxy component of the OpenStack Swift object +storage system. It can be deployed as part of its own stand-alone storage +cluster or it can be integrated with the other OpenStack components, assuming +those are also managed by Juju. For Swift to function, you'll also need to +deploy additional swift-storage nodes using the cs:precise/swift-storage +charm. + +For more information about Swift and its architecture, visit the official +project website at http://swift.openstack.org. + +This charm was developed to support deploying multiple version of Swift on +Ubuntu Precise 12.04, as they relate to the release series of OpenStack. That +is, OpenStack Essex corresponds to Swift 1.4.8 while OpenStack Folsom shipped +1.7.4. This charm can be used to deploy either (and future) versions of Swift +onto an Ubuntu Precise 12.04, making use of the Ubuntu Cloud Archive when +needed. + +Usage +----- + +Currently, Swift may be deployed in two ways. In either case, additional +storage nodes are required. The configuration option that dictates +how to deploy this charm is the 'zone-assignment' setting. This section +describes how to select the appropriate zone assignment policy, as well as +a few other configuration settings of interest. Many of the configuration +settings can be left as default. + +a. Zone Assignment + +This setting determines how the charm assignes new storage nodes to storage +zones. + +The default, 'manual' option is suggested for production as it allows +administrators to carefully architect the storage cluster. It requires each +swift-storage service to be deployed with an explicit storage zone configured +in its deployment settings.. Upon relation to a swift-proxy, the storage node +will request membership to its configured zone and be assigned by the +swift-proxy charm accordingly. Using the cs:precise/swift-storage charm with +this charm, a deployment would look something like: + + $ cat >swift.cfg <swift.cfg < Date: Tue, 18 Dec 2012 11:59:19 -0800 Subject: [PATCH 06/13] Update interfaces. --- hooks/swift-hooks.py | 12 ++++++------ ...relation-broken => swift-storage-relation-broken} | 0 ...lation-changed => swift-storage-relation-changed} | 0 hooks/swift_utils.py | 2 +- metadata.yaml | 4 +--- revision | 2 +- 6 files changed, 9 insertions(+), 11 deletions(-) rename hooks/{swift-proxy-relation-broken => swift-storage-relation-broken} (100%) rename hooks/{swift-proxy-relation-changed => swift-storage-relation-changed} (100%) diff --git a/hooks/swift-hooks.py b/hooks/swift-hooks.py index aaff33b..07d1f8a 100755 --- a/hooks/swift-hooks.py +++ b/hooks/swift-hooks.py @@ -106,12 +106,12 @@ def balance_rings(): trigger = uuid.uuid4() swift_hash = swift.get_swift_hash() # notify storage nodes that there is a new ring to fetch. - for relid in utils.relation_ids('swift-proxy'): + for relid in utils.relation_ids('swift-storage'): utils.relation_set(rid=relid, swift_hash=swift_hash, www_dir=www_dir, trigger=trigger) swift.proxy_control('restart') -def proxy_changed(): +def storage_changed(): account_port = utils.config_get('account-ring-port') object_port = utils.config_get('object-ring-port') container_port = utils.config_get('container-ring-port') @@ -124,7 +124,7 @@ def proxy_changed(): 'container_port': utils.relation_get('container_port'), } if None in node_settings.itervalues(): - utils.juju_log('INFO', 'proxy_changed: Relation not ready.') + utils.juju_log('INFO', 'storage_changed: Relation not ready.') return None for k in ['zone', 'account_port', 'object_port', 'container_port']: @@ -144,7 +144,7 @@ def proxy_changed(): if swift.should_balance([r for r in swift.SWIFT_RINGS.itervalues()]): balance_rings() -def proxy_broken(): +def storage_broken(): swift.write_apache_config() def config_changed(): @@ -159,8 +159,8 @@ hooks = { 'config-changed': config_changed, 'identity-service-relation-joined': keystone_joined, 'identity-service-relation-changed': keystone_changed, - 'swift-proxy-relation-changed': proxy_changed, - 'swift-proxy-relation-broken': proxy_broken, + 'swift-storage-relation-changed': storage_changed, + 'swift-storage-relation-broken': storage_broken, } utils.do_hooks(hooks) diff --git a/hooks/swift-proxy-relation-broken b/hooks/swift-storage-relation-broken similarity index 100% rename from hooks/swift-proxy-relation-broken rename to hooks/swift-storage-relation-broken diff --git a/hooks/swift-proxy-relation-changed b/hooks/swift-storage-relation-changed similarity index 100% rename from hooks/swift-proxy-relation-changed rename to hooks/swift-storage-relation-changed diff --git a/hooks/swift_utils.py b/hooks/swift_utils.py index b7f9153..34e0784 100644 --- a/hooks/swift_utils.py +++ b/hooks/swift_utils.py @@ -372,7 +372,7 @@ def write_apache_config(): utils.juju_log('INFO', 'Updating %s.' % APACHE_CONF) allowed_hosts = [] - for relid in utils.relation_ids('swift-proxy'): + for relid in utils.relation_ids('swift-storage'): for unit in utils.relation_list(relid): host = utils.relation_get('private-address', unit, relid) allowed_hosts.append(utils.get_host_ip(host)) diff --git a/metadata.yaml b/metadata.yaml index f1e3875..3ff4d31 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -5,10 +5,8 @@ description: | Swift is a distributed virtual object store. This formula deploys the proxy node to be related to storage nodes. provides: - swift-proxy: - interface: swift object-store: - interface: swift + interface: swift-proxy requires: swift-storage: interface: swift diff --git a/revision b/revision index 398050c..a9c8fe8 100644 --- a/revision +++ b/revision @@ -1 +1 @@ -101 +103 From 665a722348ebb476b47fa451eec2825339458167 Mon Sep 17 00:00:00 2001 From: Adam Gandelman Date: Wed, 19 Dec 2012 14:31:47 -0800 Subject: [PATCH 07/13] Use relation data 'auth_host' instead of 'private-address' for Keystone relation. --- hooks/swift_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/swift_utils.py b/hooks/swift_utils.py index 34e0784..1dc2901 100644 --- a/hooks/swift_utils.py +++ b/hooks/swift_utils.py @@ -143,7 +143,7 @@ def get_keystone_auth(): ks_auth = { 'auth_type': 'keystone', 'auth_protocol': 'http', - 'keystone_host': utils.relation_get('private-address', + 'keystone_host': utils.relation_get('auth_host', unit, relid), 'auth_port': utils.relation_get('auth_port', unit, relid), 'service_user': utils.relation_get('service_username', unit, relid), From bc52e656c0af552ff5a6e9508b220ffd0fbc3641 Mon Sep 17 00:00:00 2001 From: Adam Gandelman Date: Wed, 19 Dec 2012 14:34:28 -0800 Subject: [PATCH 08/13] Remove duplication of swift config dir creation/ownership, use swift_utils.py. --- hooks/swift-hooks.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/hooks/swift-hooks.py b/hooks/swift-hooks.py index 07d1f8a..0355549 100755 --- a/hooks/swift-hooks.py +++ b/hooks/swift-hooks.py @@ -20,12 +20,6 @@ def install(): pkgs = swift.determine_packages(rel) utils.install(*pkgs) - uid, gid = swift.swift_user() - conf_dir = os.path.dirname(swift.SWIFT_CONF) - if not os.path.isdir(conf_dir): - os.mkdir(conf_dir, 0750) - os.chown(conf_dir, uid, gid) - swift.ensure_swift_dir() # initialize swift configs. From e68695893faa8d4f37e3f9897defa49eeb8f0b53 Mon Sep 17 00:00:00 2001 From: Adam Gandelman Date: Wed, 19 Dec 2012 14:37:19 -0800 Subject: [PATCH 09/13] Add copyright. --- copyright | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 copyright diff --git a/copyright b/copyright new file mode 100644 index 0000000..a6ddcf8 --- /dev/null +++ b/copyright @@ -0,0 +1,17 @@ +Format: http://dep.debian.net/deps/dep5/ + +Files: * +Copyright: Copyright 2012, Canonical Ltd., All Rights Reserved. +License: GPL-3 + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + . + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program. If not, see . From 3b624d6fbf561f8a54536308122994ccd0e217c5 Mon Sep 17 00:00:00 2001 From: Adam Gandelman Date: Wed, 19 Dec 2012 14:46:22 -0800 Subject: [PATCH 10/13] Clean up, fix some typos in README. --- README | 8 ++++---- hooks/test.py | 4 ---- swift-proxy.yaml | 13 ------------- 3 files changed, 4 insertions(+), 21 deletions(-) delete mode 100644 hooks/test.py delete mode 100644 swift-proxy.yaml diff --git a/README b/README index fe0d8e4..d7fea26 100644 --- a/README +++ b/README @@ -30,7 +30,7 @@ settings can be left as default. a. Zone Assignment -This setting determines how the charm assignes new storage nodes to storage +This setting determines how the charm assigns new storage nodes to storage zones. The default, 'manual' option is suggested for production as it allows @@ -75,7 +75,7 @@ zones to meet its minimum replica requirement, in this case 3. The other option for zone assignment is 'auto.' In this mode, swift-proxy gets a relation to a single swift-storage service unit. Each machine unit -assinged to that service unit will be distributed evenly across zones. +assigned to that service unit will be distributed evenly across zones. $ cat >swift.cfg < Date: Wed, 19 Dec 2012 14:55:36 -0800 Subject: [PATCH 11/13] Convert README to README.md --- README => README.md | 35 +++++++++++++++--------------- hooks/object-store-relation-joined | 1 + 2 files changed, 18 insertions(+), 18 deletions(-) rename README => README.md (91%) create mode 120000 hooks/object-store-relation-joined diff --git a/README b/README.md similarity index 91% rename from README rename to README.md index d7fea26..5628851 100644 --- a/README +++ b/README.md @@ -8,8 +8,7 @@ those are also managed by Juju. For Swift to function, you'll also need to deploy additional swift-storage nodes using the cs:precise/swift-storage charm. -For more information about Swift and its architecture, visit the official -project website at http://swift.openstack.org. +For more information about Swift and its architecture, visit the [official project website](http://swift.openstack.org) This charm was developed to support deploying multiple version of Swift on Ubuntu Precise 12.04, as they relate to the release series of OpenStack. That @@ -28,7 +27,7 @@ describes how to select the appropriate zone assignment policy, as well as a few other configuration settings of interest. Many of the configuration settings can be left as default. -a. Zone Assignment +**Zone Assignment** This setting determines how the charm assigns new storage nodes to storage zones. @@ -42,18 +41,18 @@ swift-proxy charm accordingly. Using the cs:precise/swift-storage charm with this charm, a deployment would look something like: $ cat >swift.cfg < Date: Wed, 19 Dec 2012 15:19:16 -0800 Subject: [PATCH 12/13] Fix: still need to get uid+gid of swift user in install hook. --- hooks/swift-hooks.py | 1 + revision | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/hooks/swift-hooks.py b/hooks/swift-hooks.py index 0355549..d255c38 100755 --- a/hooks/swift-hooks.py +++ b/hooks/swift-hooks.py @@ -51,6 +51,7 @@ def install(): # configure a directory on webserver for distributing rings. if not os.path.isdir(swift.WWW_DIR): os.mkdir(swift.WWW_DIR, 0755) + uid, gid = swift.swift_user() os.chown(swift.WWW_DIR, uid, gid) swift.write_apache_config() diff --git a/revision b/revision index a9c8fe8..b16e5f7 100644 --- a/revision +++ b/revision @@ -1 +1 @@ -103 +104 From d073706a204a4690010fbbcde861fdee504b4da0 Mon Sep 17 00:00:00 2001 From: Adam Gandelman Date: Mon, 7 Jan 2013 14:54:50 -0800 Subject: [PATCH 13/13] Sync openstack_common.py. --- hooks/lib/openstack_common.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/hooks/lib/openstack_common.py b/hooks/lib/openstack_common.py index 97733a8..6efb14c 100644 --- a/hooks/lib/openstack_common.py +++ b/hooks/lib/openstack_common.py @@ -30,8 +30,6 @@ swift_codenames = { } def juju_log(msg): - print msg - return subprocess.check_call(['juju-log', msg])