Programmer Thoughts

By John Dickinson

OpenStack Swift on Raspberry Pi

March 24, 2013

Every attendee at PyCon 2013 got a free Raspberry Pi. So, naturally, the first thing I did was set up OpenStack Swift on it.

With thanks to North Coast Brewing for Old Rasputin and Thumbtack for the beer mug, I give you a single annotated script you can use to install OpenStack Swift, the large scale distributed storage engine, onto a low-powered credit-card-sized computer.

start

The current version of this script can be found in my github account.

    #!/bin/bash

    # This annotated script sets up a limited deployment of OpenStack Swift
    # onto a Raspberry Pi. It sets up a one-replica, one-server environment
    # appropriate for external testing. It assumes there is a user called "pi"
    # and that user has sudo access (this is the default on a Raspberry Pi).


    set -e

    # install requirements
    # I assume you've already done an `apt-get update && apt-get upgrade`

    sudo apt-get install python-software-properties curl gcc git memcached \
        python-coverage python-dev python-nose python-setuptools \
        python-simplejson python-xattr sqlite3 xfsprogs python-eventlet \
        python-greenlet python-pastedeploy python-netifaces python-pip \
        python-sphinx
    sudo pip install mock tox dnspython


    # build loopback drive
    sudo mkdir -p /srv
    sudo truncate -s 1GB /srv/swift-disk
    sudo mkfs.xfs -f -i size=512 /srv/swift-disk

    # update /etc/fstab
    grep '/srv/swift-disk' /etc/fstab
    if [ $? = 1 ]; then
    sudo tee -a /etc/fstab >/dev/null <<EOF

    /srv/swift-disk /mnt/sdb1 xfs loop,noatime,nodiratime,nobarrier,inode64,logbufs=8 0 0
    EOF
    fi

    sudo mkdir -p /mnt/sdb1/1

    sudo chown -R pi:pi /mnt/sdb1/1
    sudo ln -fs /mnt/sdb1/1 /srv/1
    sudo chown -R pi:pi /etc/swift /srv/1/ /var/run/swift

    # update /etc/rc.local
    grep 'su - pi /home/pi/bin/startmain' /etc/rc.local
    if [ $? = 1 ]; then
    sudo tee -a /etc/rc.local >/dev/null <<EOF

    mkdir -p /var/cache/swift
    chown pi:pi /var/cache/swift*
    mkdir -p /var/run/swift
    chown pi:pi /var/run/swift
    su - pi /home/pi/bin/startmain
    EOF
    fi

    sudo tee /etc/rsyncd.conf >/dev/null <<EOF
    uid = pi
    gid = pi
    log file = /var/log/rsyncd.log
    pid file = /var/run/rsyncd.pid
    address = 127.0.0.1

    [account6012]
    max connections = 25
    path = /srv/1/node/
    read only = false
    lock file = /var/lock/account6012.lock


    [container6011]
    max connections = 25
    path = /srv/1/node/
    read only = false
    lock file = /var/lock/container6011.lock

    [object6010]
    max connections = 25
    path = /srv/1/node/
    read only = false
    lock file = /var/lock/object6010.lock
    EOF

    sudo tee /etc/rsyslog.d/10-swift.conf >/dev/null <<EOF
    # Uncomment the following to have a log containing all logs together
    local1,local2,local3,local4,local5.*   /var/log/swift/all.log

    # Uncomment the following to have hourly proxy logs for stats processing
    $template HourlyProxyLog,"/var/log/swift/hourly/%\$YEAR%%\$MONTH%%\$DAY%%\$HOUR%"
    local1.*;local1.!notice ?HourlyProxyLog

    local1.*;local1.!notice /var/log/swift/proxy.log
    local1.notice           /var/log/swift/proxy.error
    local1.*                ~

    local2.*;local2.!notice /var/log/swift/storage1.log
    local2.notice           /var/log/swift/storage1.error
    local2.*                ~
    EOF

    sudo mkdir -p /var/log/swift/hourly
    sudo chmod -R g+w /var/log/swift



    set +e
    cd && git clone git://github.com/openstack/python-swiftclient.git
    set -e
    cd ~/python-swiftclient; git pull origin master && sudo python ./setup.py develop

    set +e
    cd && git clone git://github.com/openstack/swift.git
    set -e
    cd ~/swift; git pull origin master && sudo python ./setup.py develop

    cd && mkdir -p ~/bin

    sudo mkdir -p /etc/swift
    sudo chown pi:pi /etc/swift


    cat >/etc/swift/proxy-server.conf <<EOF
    [DEFAULT]
    bind_port = 8080
    user = pi
    log_facility = LOG_LOCAL1
    log_level = DEBUG
    eventlet_debug = true

    [pipeline:main]
    pipeline = catch_errors healthcheck proxy-logging cache slo ratelimit tempurl formpost tempauth staticweb container-quotas account-quotas proxy-logging proxy-server

    [app:proxy-server]
    use = egg:swift#proxy
    allow_account_management = true
    account_autocreate = true

    [filter:tempauth]
    use = egg:swift#tempauth
    user_admin_admin = admin .admin .reseller_admin
    user_test_tester = testing .admin
    user_test2_tester2 = testing2 .admin
    user_test4_tester4 = testing4 .admin
    user_test_tester3 = testing3
    user_demo_demo = demo .admin

    [filter:catch_errors]
    use = egg:swift#catch_errors

    [filter:healthcheck]
    use = egg:swift#healthcheck

    [filter:cache]
    use = egg:swift#memcache

    [filter:proxy-logging]
    use = egg:swift#proxy_logging

    [filter:ratelimit]
    use = egg:swift#ratelimit

    [filter:domain_remap]
    use = egg:swift#domain_remap

    [filter:cname_lookup]
    # Note: this middleware requires python-dnspython
    use = egg:swift#cname_lookup

    [filter:staticweb]
    use = egg:swift#staticweb

    [filter:formpost]
    use = egg:swift#formpost

    [filter:list-endpoints]
    use = egg:swift#list_endpoints

    [filter:bulk]
    use = egg:swift#bulk

    [filter:container-quotas]
    use = egg:swift#container_quotas

    [filter:account-quotas]
    use = egg:swift#account_quotas

    [filter:slo]
    use = egg:swift#slo

    [filter:tempurl]
    use = egg:swift#tempurl

    [filter:formpost]
    use = egg:swift#formpost
    EOF

    cat >/etc/swift/account-server.conf <<EOF
    [DEFAULT]
    devices = /srv/1/node/
    bind_port = 6012
    user = pi
    log_facility = LOG_LOCAL2
    recon_cache_path = /var/cache/swift
    eventlet_debug = true
    log_level = DEBUG
    mount_check = false
    disable_fallocate = true

    [pipeline:main]
    pipeline = recon account-server

    [app:account-server]
    use = egg:swift#account

    [filter:recon]
    use = egg:swift#recon

    [account-replicator]
    vm_test_mode = yes

    [account-auditor]

    [account-reaper]
    EOF

    cat >/etc/swift/container-server.conf <<EOF
    [DEFAULT]
    devices = /srv/1/node/
    bind_port = 6011
    user = pi
    log_facility = LOG_LOCAL2
    recon_cache_path = /var/cache/swift
    eventlet_debug = true
    log_level = DEBUG
    mount_check = false
    disable_fallocate = true

    [pipeline:main]
    pipeline = recon container-server

    [app:container-server]
    use = egg:swift#container

    [filter:recon]
    use = egg:swift#recon

    [container-replicator]
    vm_test_mode = yes

    [container-updater]

    [container-auditor]

    [container-sync]
    EOF

    cat >/etc/swift/object-server.conf <<EOF
    [DEFAULT]
    devices = /srv/1/node/
    bind_port = 6010
    user = pi
    log_facility = LOG_LOCAL2
    recon_cache_path = /var/cache/swift
    eventlet_debug = true
    log_level = DEBUG
    mount_check = false
    disable_fallocate = true

    [pipeline:main]
    pipeline = recon object-server

    [app:object-server]
    use = egg:swift#object

    [filter:recon]
    use = egg:swift#recon

    [object-replicator]
    vm_test_mode = yes

    [object-updater]

    [object-auditor]
    EOF

    # when setting up the hash_path_suffix, it is important to make it unique
    # and keep it a secret
    SUFF=`python -c 'import uuid; print uuid.uuid4().hex'`
    cat <<EOF >/etc/swift/swift.conf
    [swift-hash]
    swift_hash_path_suffix = $SUFF

    [swift-constraints]
    #max_file_size = 5368709122
    # Note: Since the Raspberry Pi has such limited storage space,
    # the maximum size of a single object has been set to 500MB.
    max_file_size = 524288000
    #max_meta_name_length = 128
    #max_meta_value_length = 256
    #max_meta_count = 90
    #max_meta_overall_size = 4096
    #max_object_name_length = 1024
    #container_listing_limit = 10000
    #account_listing_limit = 10000
    #max_account_name_length = 256
    #max_container_name_length = 256
    EOF


    cat <<EOF >/home/pi/bin/remakerings
    #!/bin/bash

    cd /etc/swift

    rm -f *.builder *.ring.gz backups/*.builder backups/*.ring.gz

    swift-ring-builder object.builder create 8 1 0
    swift-ring-builder object.builder add r1z1-127.0.0.1:6010/d1 1
    swift-ring-builder object.builder rebalance
    swift-ring-builder container.builder create 8 1 0
    swift-ring-builder container.builder add r1z1-127.0.0.1:6011/d1 1
    swift-ring-builder container.builder rebalance
    swift-ring-builder account.builder create 8 1 0
    swift-ring-builder account.builder add r1z1-127.0.0.1:6012/d1 1
    swift-ring-builder account.builder rebalance
    EOF

    cat <<EOF >/home/pi/bin/resetswift
    #!/bin/bash

    swift-init all stop

    sudo umount /srv/swift-disk
    sudo mkdir -p /srv
    sudo truncate -s 1GB /srv/swift-disk
    sudo mkfs.xfs -f -i size=512 /srv/swift-disk

    sudo mount -a
    sudo mkdir -p /mnt/sdb1/1
    sudo chown -R pi:pi /mnt/sdb1/*

    sudo rm -rf /var/log/swift
    sudo mkdir -p /var/log/swift/hourly

    sudo mkdir /var/cache/swift
    sudo chown -R pi:pi /var/cache/swift

    find /var/cache/swift* -type f -name *.recon -exec rm -f {} \;

    sudo service rsyslog restart
    sudo service memcached restart
    EOF

    cat <<EOF >/home/pi/bin/startmain
    #!/bin/bash

    if [ ! -d /var/run/swift ]; then
      sudo mkdir -p /var/run/swift
      sudo chown -R pi:pi /var/run/swift
    fi

    swift-init main start
    EOF

    chmod +x /home/pi/bin/*

    cat <<EOF

    ===========================================

    Install completed.

    You can now call \`resetswift\` and \`startmain\` to clean everything and start
    the Swift server processes.

    To test, try the following:
    export PIIP=<IP address of your Raspberry Pi>
    curl -i -H "X-Auth-User: test:tester" -H "X-Auth-Key: testing" \\
       http://\${PIIP}:8080/auth/v1.0/
    EOF

This was pretty fun for me. I hope you liked it too.

end

This work is licensed under a Creative Commons Attribution 3.0 Unported License.

The thoughts expressed here are my own and do not necessarily represent those of my employer.