#!/bin/sh
# Install and Enable all OpenStack services on a single node,
# for testing/demonstration purposes.
#
# Copyright (C) 2012, Red Hat, Inc.
# Pádraig Brady <pbrady@redhat.com>
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#

service_installed() {
  PAGER= systemctl show $1.service >/dev/null 2>&1 ||
  chkconfig --list $1 >//dev/null 2>&1
}

set -e # exit on error. This script is restartable

if [ $(id -u) -ne 0 ]; then
  echo 'Please run this as the root user' >&2
  exit 1
fi


echo "======= Installing/Updating packages ======"

#install nova-common first to see what version it is
yum install openstack-nova-common -y

#determine which OpenStack version we'll be running
os_version=$(rpm -q openstack-nova-common --qf='%{VERSION}')
case $os_version in
  2012.1|2012.1.*) os_dist='essex';;
  2012.2|2012.2.*) os_dist='folsom';;
esac

# Install these first if not on essex
if [ "$os_dist" != 'essex' ]; then
  yum install -y openstack-cinder openstack-swift-plugin-swift3
  cinder='cinder'
fi
# memcached is needed by swift
# qpid AMQP broker is needed by most services
# virtualization capabilities are needed by nova
# avahi referenced explicitly to avoid https://bugzilla.redhat.com/746111
yum -y install \
 qpid-cpp-server qpid-cpp-server-daemon \
 @virtualization \
 memcached \
 avahi \
 openstack-nova openstack-nova-novncproxy \
 openstack-glance openstack-keystone openstack-dashboard \
 openstack-swift\* openstack-quantum \
 openstack-utils virt-what \
 -x openstack-swift-plugin-swift3

if [ "$os_dist" != 'essex' ] && [ "$os_dist" != 'folsom' ]; then
  conductor='conductor'
fi

echo "======= Setting up the databases ======"

getpassword()
{
  password='x'
  until [ "$password" = "$rpassword" ]; do
    read -s -p "Enter a password for the '$1' user : " password; echo >&2
    read -s -p "Reenter password for the '$1' user : " rpassword; echo >&2
  done
  echo "$password"
}

ROOT_DB_PW=$(getpassword 'database root')

for APP in nova glance keystone $cinder; do
  openstack-db -y --init --service $APP --rootpw "$ROOT_DB_PW"
done

if virt-what | grep -q .; then
  # We're running in a VM so set nova-compute up appropriately
  echo '======= Configuring VM. Please wait =======' >&2
  fedora_ver="0$(sed -n 's/Fedora release \([0-9]*\).*/\1/p' /etc/issue)"
  openstack-config --set /etc/nova/nova.conf DEFAULT libvirt_type qemu
  # Workaround (https://bugzilla.redhat.com/show_bug.cgi?id=858216)
  openstack-config --set /etc/nova/nova.conf DEFAULT libvirt_cpu_mode none
  if [ "$fedora_ver" -ge 16 ] && selinuxenabled; then
    setsebool -P virt_use_execmem on
  fi
  # Note if running on RHEL (derivatives) <= 6.3 or
  # specifically libvirt < 0.9.13-66 then we need to
  # do this to support starting nested VMs. See:
  # See https://bugzilla.redhat.com/show_bug.cgi?id=813735
  # A local libvirt version check is fine here since this script
  # only considers installing everything on the local node.
  if python -c 'import libvirt, sys; sys.exit(not libvirt.getVersion() <= 9013)'; then
    [ "$(uname -m)" = 'x86_64' ] && qemu_cmd=qemu-system-x86_64 || qemu_cmd=qemu-system-i386
    if [ -e /usr/libexec/qemu-kvm ]; then # present on RHEL
      ln -s /usr/libexec/qemu-kvm /usr/bin/$qemu_cmd
      service libvirtd stop # (re)started below
    fi
  fi
fi

# Allow httpd (horizon) to connect to other services
selinuxenabled && setsebool -P httpd_can_network_connect=on
service httpd restart

# Configure cinder with nova and keystone if installed
if [ "$cinder" ]; then
  # Change nova configuration to use cinder
  openstack-config --set /etc/nova/nova.conf DEFAULT volume_api_class nova.volume.cinder.API
  # The following is the default apis with 'osapi_volume' removed
  openstack-config --set /etc/nova/nova.conf DEFAULT enabled_apis ec2,osapi_compute,metadata

  # On RHEL, manually adjust config to integrate
  # persistent volumes on tgtd startup
  if rpm -q scsi-target-utils | grep -q el6; then
    sed -i '1iinclude /etc/cinder/volumes/*' /etc/tgt/targets.conf
  fi
fi

# TODO support volumes (maybe as an option due to size)
# Need to support setup on reboot also.
# When this is done, add 'volume' to the nova services below
# truncate -s20G /var/lib/nova/nova-volumes.img
# vgcreate nova-volumes $(sudo losetup --show -f /var/lib/nova/nova-volumes.img)

# determine the correct dbus service name
service_installed dbus && dbus='dbus' || dbus='messagebus'

# Ensure cgroup is started so that libvirt does an SELinux relabel
# for VM disk images.  Avoids a bug on some versions of libvirtd on RHEL 6.4
service_installed cgroup && cgroup='cgroup'

echo "======= Enabling the services ======"

for svc in qpidd $cgroup $dbus libvirtd httpd; do
    chkconfig $svc on
done
for svc in api registry; do
    chkconfig openstack-glance-$svc on
done
for svc in api objectstore compute network scheduler cert $conductor consoleauth novncproxy; do
    chkconfig openstack-nova-$svc on
done
if [ "$cinder" ]; then
  for svc in api scheduler; do
    chkconfig openstack-cinder-$svc on
  done
fi

# Disable QPID auth with a warning at the end
if grep -q '^auth=yes' /etc/qpidd.conf; then
  sed -i 's/^auth=yes/auth=no/' /etc/qpidd.conf
  qpid_auth_disabled=1
fi

echo "======= Starting the services ======"

for svc in qpidd $cgroup $dbus libvirtd httpd; do
    service $svc start
done
for svc in api registry; do
    service openstack-glance-$svc start
done
for svc in api objectstore compute network scheduler cert $conductor consoleauth novncproxy; do
    service openstack-nova-$svc start
done
if [ "$cinder" ]; then
  for svc in api scheduler; do
    service openstack-cinder-$svc start
  done
fi

echo "======= Setting up Keystone ======"

# Set up a keystonerc file with admin password
cat > ~/keystonerc <<EOF
export OS_USERNAME=admin
export OS_PASSWORD=verybadpass
export OS_TENANT_NAME=admin
export OS_AUTH_URL=http://127.0.0.1:5000/v2.0/
EOF

# Service token is needed to bootstrap keystone commands
export SERVICE_TOKEN=$(openssl rand -hex 10)
export SERVICE_ENDPOINT=http://127.0.0.1:35357/v2.0

. ~/keystonerc

# Set the administrative token in the config file
openstack-config --set /etc/keystone/keystone.conf DEFAULT admin_token $SERVICE_TOKEN

#Generate keystone certs
if [ "$os_dist" != 'essex' ] && [ "$os_dist" != 'folsom' ]; then
  keystone-manage pki_setup
  chown -R keystone /etc/keystone/ssl/
fi

# Stop keystone if it is already running (to reload the new admin token)
service openstack-keystone status >/dev/null 2>&1 &&
  service openstack-keystone stop

# Start and enable the Keystone service
service openstack-keystone start
chkconfig openstack-keystone on

# wait for the keystone service to start
tries=0
until keystone user-list >/dev/null 2>&1; do
  tries=$(($tries + 1))
  [ $tries -eq 10 ] && { keystone user-list; break; }
  sleep 1
done

# Create sample Tenants, Users and Roles
ADMIN_PASSWORD=$OS_PASSWORD SERVICE_PASSWORD=servicepass \
ENABLE_SWIFT=1 openstack-keystone-sample-data

# Change nova configuration to use keystone
openstack-config --set /etc/nova/nova.conf DEFAULT auth_strategy keystone
if [ "$os_dist" == 'essex' ]; then
  openstack-config --set /etc/nova/api-paste.ini filter:authtoken admin_tenant_name service
  openstack-config --set /etc/nova/api-paste.ini filter:authtoken admin_user nova
  openstack-config --set /etc/nova/api-paste.ini filter:authtoken admin_password servicepass
else #>=folsom
  openstack-config --set /etc/nova/nova.conf keystone_authtoken admin_tenant_name service
  openstack-config --set /etc/nova/nova.conf keystone_authtoken admin_user nova
  openstack-config --set /etc/nova/nova.conf keystone_authtoken admin_password servicepass
fi
for svc in api compute; do
    service openstack-nova-$svc restart
done

# Change glance configuration to use keystone
for svc in api registry; do
  openstack-config --set /etc/glance/glance-$svc.conf paste_deploy flavor keystone
  if [ "$os_dist" == 'essex' ]; then
    openstack-config --set /etc/glance/glance-$svc-paste.ini filter:authtoken admin_tenant_name service
    openstack-config --set /etc/glance/glance-$svc-paste.ini filter:authtoken admin_user glance
    openstack-config --set /etc/glance/glance-$svc-paste.ini filter:authtoken admin_password servicepass
  else #>=folsom
    openstack-config --set /etc/glance/glance-$svc.conf keystone_authtoken admin_tenant_name service
    openstack-config --set /etc/glance/glance-$svc.conf keystone_authtoken admin_user glance
    openstack-config --set /etc/glance/glance-$svc.conf keystone_authtoken admin_password servicepass
  fi
done
for svc in api registry; do
    service openstack-glance-$svc restart
done

cinder_keystone_sample_data()
{
  # TODO: Move this to openstack-keystone-sample-data

  get_id () { echo $("$@" | grep ' id ' | awk '{print $4}'); }
  ADMIN_PASSWORD=$OS_PASSWORD
  SERVICE_HOST=127.0.0.1
  SERVICE_PASSWORD=servicepass
  SERVICE_TENANT=$(keystone tenant-list | grep service | awk '{print $2}')
  ADMIN_ROLE=$(keystone role-list | grep ' admin ' | awk '{print $2}')

  CINDER_USER=$(get_id keystone user-create --name=cinder \
                                            --pass="$SERVICE_PASSWORD" \
                                            --tenant_id $SERVICE_TENANT \
                                            --email=cinder@example.com)
  keystone user-role-add --tenant_id $SERVICE_TENANT \
                         --user_id $CINDER_USER \
                         --role_id $ADMIN_ROLE
  CINDER_SERVICE=$(get_id keystone service-create \
      --name=cinder \
      --type=volume \
      --description="Cinder Service")
  keystone endpoint-create \
      --region RegionOne \
      --service_id $CINDER_SERVICE \
      --publicurl "http://$SERVICE_HOST:8776/v1/\$(tenant_id)s" \
      --adminurl "http://$SERVICE_HOST:8776/v1/\$(tenant_id)s" \
      --internalurl "http://$SERVICE_HOST:8776/v1/\$(tenant_id)s"
}

# Change cinder configuration to use keystone
if [ "$cinder" ]; then
  openstack-config --set /etc/cinder/cinder.conf DEFAULT auth_strategy keystone
  openstack-config --set /etc/cinder/cinder.conf keystone_authtoken admin_tenant_name service
  openstack-config --set /etc/cinder/cinder.conf keystone_authtoken admin_user cinder
  openstack-config --set /etc/cinder/cinder.conf keystone_authtoken admin_password servicepass

  cinder_keystone_sample_data

  # Start the services
  # Note volume requires the cinder-volumes volume group
  # to be available, so we don't enable that by default
  for svc in api scheduler; do
    service openstack-cinder-$svc restart
  done
fi

swift_keystone_sample_data()
{
  # TODO: Move this to openstack-keystone-sample-data

  # Add the swift endpoint
  SWIFTSERIVEID=$(keystone service-list | grep object-store | awk '{print $2}')
  keystone endpoint-create --service_id $SWIFTSERIVEID \
                           --publicurl   'http://127.0.0.1:8080/v1/AUTH_$(tenant_id)s' \
                           --adminurl    'http://127.0.0.1:8080/v1/AUTH_$(tenant_id)s' \
                           --internalurl 'http://127.0.0.1:8080/v1/AUTH_$(tenant_id)s'
}

setup_swift()
{
  echo "======= Setting up Swift ======"

  # edit default config
  openstack-config --set /etc/swift/proxy-server.conf filter:authtoken admin_tenant_name service
  openstack-config --set /etc/swift/proxy-server.conf filter:authtoken admin_user swift
  openstack-config --set /etc/swift/proxy-server.conf filter:authtoken admin_password servicepass
  openstack-config --set /etc/swift/swift.conf swift-hash swift_hash_path_suffix $(openssl rand -hex 10)

  swift_keystone_sample_data

  # Create ringfiles and storage devices
  swift-ring-builder /etc/swift/account.builder create 12 3 1
  swift-ring-builder /etc/swift/container.builder create 12 3 1
  swift-ring-builder /etc/swift/object.builder create 12 3 1
  for zone in 1 2 3 4; do
    truncate /var/tmp/swift-storage-$zone --size 5G
    DEVICE=$(losetup --show -f  /var/tmp/swift-storage-$zone)
    mkfs.ext4 -I 1024 $DEVICE
    mkdir -p /srv/node/device$zone
    mount -o noatime,nodiratime,nobarrier,user_xattr $DEVICE /srv/node/device$zone
    swift-ring-builder /etc/swift/account.builder add z$zone-127.0.0.1:6002/device$zone 100
    swift-ring-builder /etc/swift/container.builder add z$zone-127.0.0.1:6001/device$zone 100
    swift-ring-builder /etc/swift/object.builder add z$zone-127.0.0.1:6000/device$zone 100
  done
  swift-ring-builder /etc/swift/account.builder rebalance
  swift-ring-builder /etc/swift/container.builder rebalance
  swift-ring-builder /etc/swift/object.builder rebalance

  # Make sure swift owns the ring file
  chown -R swift:swift /etc/swift/*gz /srv/node

  # Start the services
  service memcached start
  chkconfig memcached on
  service openstack-swift-proxy start
  chkconfig openstack-swift-proxy on
  for ringtype in account container object; do
    service openstack-swift-$ringtype start
    chkconfig openstack-swift-$ringtype on
    for service in replicator updater auditor; do
      if [ $ringtype != 'account' ] || [ $service != 'updater' ]; then
        swift-init $ringtype-$service start
      fi
    done
  done
}

# We can only setup Swift for packages that contain example config files
[ -e /etc/swift/proxy-server.conf ] && setup_swift

echo "======= Running openstack-status ======"
openstack-status

echo "The OpenStack web interface is running at http://localhost/dashboard"
echo "(Use User:$OS_USERNAME and Password:$OS_PASSWORD)"

if [ "$qpid_auth_disabled" ]; then
  echo "Please note that QPID authentication has been disabled in /etc/qpidd.conf"
fi
