This should be managed in the devstack repo, since it's a base job to
run devstack.
Change-Id: Iffe54fbccbccd68db08f79a1b51dd7f76dbff408
Depends-On: Ie2119f24360d56690ffd772b95a9ea6b98dd4a39
| 1 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,85 @@ |
| 0 |
+- nodeset: |
|
| 1 |
+ name: openstack-single-node |
|
| 2 |
+ nodes: |
|
| 3 |
+ - name: controller |
|
| 4 |
+ label: ubuntu-xenial |
|
| 5 |
+ groups: |
|
| 6 |
+ - name: tempest |
|
| 7 |
+ nodes: |
|
| 8 |
+ - controller |
|
| 9 |
+ |
|
| 10 |
+- nodeset: |
|
| 11 |
+ name: openstack-two-node |
|
| 12 |
+ nodes: |
|
| 13 |
+ - name: controller |
|
| 14 |
+ label: ubuntu-xenial |
|
| 15 |
+ - name: compute1 |
|
| 16 |
+ label: ubuntu-xenial |
|
| 17 |
+ groups: |
|
| 18 |
+ - name: tempest |
|
| 19 |
+ nodes: |
|
| 20 |
+ - controller |
|
| 21 |
+ - name: compute |
|
| 22 |
+ nodes: |
|
| 23 |
+ - controller |
|
| 24 |
+ - compute1 |
|
| 25 |
+ |
|
| 26 |
+- job: |
|
| 27 |
+ name: devstack |
|
| 28 |
+ parent: multinode |
|
| 29 |
+ description: Base devstack job |
|
| 30 |
+ nodeset: openstack-single-node |
|
| 31 |
+ required-projects: |
|
| 32 |
+ - openstack-dev/devstack |
|
| 33 |
+ - openstack/cinder |
|
| 34 |
+ - openstack/glance |
|
| 35 |
+ - openstack/keystone |
|
| 36 |
+ - openstack/neutron |
|
| 37 |
+ - openstack/nova |
|
| 38 |
+ - openstack/requirements |
|
| 39 |
+ - openstack/swift |
|
| 40 |
+ timeout: 7200 |
|
| 41 |
+ vars: |
|
| 42 |
+ devstack_localrc: |
|
| 43 |
+ DATABASE_PASSWORD: secretdatabase |
|
| 44 |
+ RABBIT_PASSWORD: secretrabbit |
|
| 45 |
+ ADMIN_PASSWORD: secretadmin |
|
| 46 |
+ SERVICE_PASSWORD: secretservice |
|
| 47 |
+ NETWORK_GATEWAY: 10.1.0.1 |
|
| 48 |
+ Q_USE_DEBUG_COMMAND: True |
|
| 49 |
+ FIXED_RANGE: 10.1.0.0/20 |
|
| 50 |
+ IPV4_ADDRS_SAFE_TO_USE: 10.1.0.0/20 |
|
| 51 |
+ FLOATING_RANGE: 172.24.5.0/24 |
|
| 52 |
+ PUBLIC_NETWORK_GATEWAY: 172.24.5.1 |
|
| 53 |
+ FLOATING_HOST_PREFIX: 172.24.4 |
|
| 54 |
+ FLOATING_HOST_MASK: 23 |
|
| 55 |
+ SWIFT_REPLICAS: 1 |
|
| 56 |
+ SWIFT_START_ALL_SERVICES: False |
|
| 57 |
+ LOGFILE: /opt/stack/logs/devstacklog.txt |
|
| 58 |
+ LOG_COLOR: False |
|
| 59 |
+ VERBOSE: True |
|
| 60 |
+ NETWORK_GATEWAY: 10.1.0.1 |
|
| 61 |
+ NOVNC_FROM_PACKAGE: True |
|
| 62 |
+ ERROR_ON_CLONE: True |
|
| 63 |
+ # NOTE(dims): etcd 3.x is not available in debian/ubuntu |
|
| 64 |
+ # etc. As a stop gap measure, devstack uses wget to download |
|
| 65 |
+ # from the location below for all the CI jobs. |
|
| 66 |
+ ETCD_DOWNLOAD_URL: "http://tarballs.openstack.org/etcd/" |
|
| 67 |
+ devstack_services: |
|
| 68 |
+ horizon: False |
|
| 69 |
+ tempest: False |
|
| 70 |
+ pre-run: playbooks/pre |
|
| 71 |
+ post-run: playbooks/post |
|
| 72 |
+ |
|
| 73 |
+ |
|
| 74 |
+- project: |
|
| 75 |
+ name: openstack-dev/devstack |
|
| 76 |
+ check: |
|
| 77 |
+ jobs: |
|
| 78 |
+ - devstack: |
|
| 79 |
+ files: |
|
| 80 |
+ - ^playbooks/pre |
|
| 81 |
+ - ^playbooks/post |
|
| 82 |
+ - ^playbooks/devstack |
|
| 83 |
+ - ^roles/ |
|
| 84 |
+ - .zuul.yaml |
| 0 | 4 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,22 @@ |
| 0 |
+- hosts: all |
|
| 1 |
+ roles: |
|
| 2 |
+ - configure-swap |
|
| 3 |
+ - setup-stack-user |
|
| 4 |
+ - setup-tempest-user |
|
| 5 |
+ - setup-devstack-source-dirs |
|
| 6 |
+ - setup-devstack-log-dir |
|
| 7 |
+ - setup-devstack-cache |
|
| 8 |
+ - start-fresh-logging |
|
| 9 |
+ - write-devstack-local-conf |
|
| 10 |
+ # TODO(jeblair): remove when configure-mirrors is fixed |
|
| 11 |
+ tasks: |
|
| 12 |
+ - name: Hack mirror_info |
|
| 13 |
+ shell: |
|
| 14 |
+ _raw_params: | |
|
| 15 |
+ mkdir /etc/ci |
|
| 16 |
+ cat << "EOF" > /etc/ci/mirror_info.sh |
|
| 17 |
+ export NODEPOOL_UCA_MIRROR=http://mirror.dfw.rax.openstack.org/ubuntu-cloud-archive |
|
| 18 |
+ EOF |
|
| 19 |
+ args: |
|
| 20 |
+ executable: /bin/bash |
|
| 21 |
+ become: true |
| 0 | 22 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,11 @@ |
| 0 |
+Configure a swap partition |
|
| 1 |
+ |
|
| 2 |
+Creates a swap partition on the ephemeral block device (the rest of which |
|
| 3 |
+will be mounted on /opt). |
|
| 4 |
+ |
|
| 5 |
+**Role Variables** |
|
| 6 |
+ |
|
| 7 |
+.. zuul:rolevar:: configure_swap_size |
|
| 8 |
+ :default: 8192 |
|
| 9 |
+ |
|
| 10 |
+ The size of the swap partition, in MiB. |
| 0 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,110 @@ |
| 0 |
+# Configure attached ephemeral devices for storage and swap |
|
| 1 |
+ |
|
| 2 |
+- assert: |
|
| 3 |
+ that: |
|
| 4 |
+ - "ephemeral_device is defined" |
|
| 5 |
+ |
|
| 6 |
+- name: Set partition names |
|
| 7 |
+ set_fact: |
|
| 8 |
+ swap_partition: "{{ ephemeral_device}}1"
|
|
| 9 |
+ opt_partition: "{{ ephemeral_device}}2"
|
|
| 10 |
+ |
|
| 11 |
+- name: Ensure ephemeral device is unmounted |
|
| 12 |
+ become: yes |
|
| 13 |
+ mount: |
|
| 14 |
+ name: "{{ ephemeral_device }}"
|
|
| 15 |
+ state: unmounted |
|
| 16 |
+ |
|
| 17 |
+- name: Get existing partitions |
|
| 18 |
+ become: yes |
|
| 19 |
+ parted: |
|
| 20 |
+ device: "{{ ephemeral_device }}"
|
|
| 21 |
+ unit: MiB |
|
| 22 |
+ register: ephemeral_partitions |
|
| 23 |
+ |
|
| 24 |
+- name: Remove any existing partitions |
|
| 25 |
+ become: yes |
|
| 26 |
+ parted: |
|
| 27 |
+ device: "{{ ephemeral_device }}"
|
|
| 28 |
+ number: "{{ item.num }}"
|
|
| 29 |
+ state: absent |
|
| 30 |
+ with_items: |
|
| 31 |
+ - "{{ ephemeral_partitions.partitions }}"
|
|
| 32 |
+ |
|
| 33 |
+- name: Create new disk label |
|
| 34 |
+ become: yes |
|
| 35 |
+ parted: |
|
| 36 |
+ label: msdos |
|
| 37 |
+ device: "{{ ephemeral_device }}"
|
|
| 38 |
+ |
|
| 39 |
+- name: Create swap partition |
|
| 40 |
+ become: yes |
|
| 41 |
+ parted: |
|
| 42 |
+ device: "{{ ephemeral_device }}"
|
|
| 43 |
+ number: 1 |
|
| 44 |
+ state: present |
|
| 45 |
+ part_start: '0%' |
|
| 46 |
+ part_end: "{{ configure_swap_size }}MiB"
|
|
| 47 |
+ |
|
| 48 |
+- name: Create opt partition |
|
| 49 |
+ become: yes |
|
| 50 |
+ parted: |
|
| 51 |
+ device: "{{ ephemeral_device }}"
|
|
| 52 |
+ number: 2 |
|
| 53 |
+ state: present |
|
| 54 |
+ part_start: "{{ configure_swap_size }}MiB"
|
|
| 55 |
+ part_end: "100%" |
|
| 56 |
+ |
|
| 57 |
+- name: Make swap on partition |
|
| 58 |
+ become: yes |
|
| 59 |
+ command: "mkswap {{ swap_partition }}"
|
|
| 60 |
+ |
|
| 61 |
+- name: Write swap to fstab |
|
| 62 |
+ become: yes |
|
| 63 |
+ mount: |
|
| 64 |
+ path: none |
|
| 65 |
+ src: "{{ swap_partition }}"
|
|
| 66 |
+ fstype: swap |
|
| 67 |
+ opts: sw |
|
| 68 |
+ passno: 0 |
|
| 69 |
+ dump: 0 |
|
| 70 |
+ state: present |
|
| 71 |
+ |
|
| 72 |
+# XXX: does "parted" plugin ensure the partition is available |
|
| 73 |
+# before moving on? No udev settles here ... |
|
| 74 |
+ |
|
| 75 |
+- name: Add all swap |
|
| 76 |
+ become: yes |
|
| 77 |
+ command: swapon -a |
|
| 78 |
+ |
|
| 79 |
+- name: Create /opt filesystem |
|
| 80 |
+ become: yes |
|
| 81 |
+ filesystem: |
|
| 82 |
+ fstype: ext4 |
|
| 83 |
+ dev: "{{ opt_partition }}"
|
|
| 84 |
+ |
|
| 85 |
+# Rackspace at least does not have enough room for two devstack |
|
| 86 |
+# installs on the primary partition. We copy in the existing /opt to |
|
| 87 |
+# the new partition on the ephemeral device, and then overmount /opt |
|
| 88 |
+# to there for the test runs. |
|
| 89 |
+# |
|
| 90 |
+# NOTE(ianw): the existing "mount" touches fstab. There is currently (Sep2017) |
|
| 91 |
+# work in [1] to split mount & fstab into separate parts, but for now we bundle |
|
| 92 |
+# it into an atomic shell command |
|
| 93 |
+# [1] https://github.com/ansible/ansible/pull/27174 |
|
| 94 |
+- name: Copy old /opt |
|
| 95 |
+ become: yes |
|
| 96 |
+ shell: | |
|
| 97 |
+ mount {{ opt_partition }} /mnt
|
|
| 98 |
+ find /opt/ -mindepth 1 -maxdepth 1 -exec mv {} /mnt/ \;
|
|
| 99 |
+ umount /mnt |
|
| 100 |
+ |
|
| 101 |
+# This overmounts any existing /opt |
|
| 102 |
+- name: Add opt to fstab and mount |
|
| 103 |
+ become: yes |
|
| 104 |
+ mount: |
|
| 105 |
+ path: /opt |
|
| 106 |
+ src: "{{ opt_partition }}"
|
|
| 107 |
+ fstype: ext4 |
|
| 108 |
+ opts: noatime |
|
| 109 |
+ state: mounted |
| 0 | 110 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,63 @@ |
| 0 |
+# On RAX hosts, we have a small root partition and a large, |
|
| 1 |
+# unallocated ephemeral device attached at /dev/xvde |
|
| 2 |
+- name: Set ephemeral device if /dev/xvde exists |
|
| 3 |
+ when: ansible_devices["xvde"] is defined |
|
| 4 |
+ set_fact: |
|
| 5 |
+ ephemeral_device: "/dev/xvde" |
|
| 6 |
+ |
|
| 7 |
+# On other providers, we have a device called "ephemeral0". |
|
| 8 |
+# |
|
| 9 |
+# NOTE(ianw): Once [1] is in our ansible (2.4 era?), we can figure |
|
| 10 |
+# this out more directly by walking the device labels in the facts |
|
| 11 |
+# |
|
| 12 |
+# [1] https://github.com/ansible/ansible/commit/d46dd99f47c0ee5081d15bc5b741e9096d8bfd3e |
|
| 13 |
+- name: Set ephemeral device by label |
|
| 14 |
+ when: ephemeral_device is undefined |
|
| 15 |
+ block: |
|
| 16 |
+ - name: Get ephemeral0 device node |
|
| 17 |
+ command: /sbin/blkid -L ephemeral0 |
|
| 18 |
+ register: ephemeral0 |
|
| 19 |
+ # If this doesn't exist, returns !0 |
|
| 20 |
+ ignore_errors: yes |
|
| 21 |
+ changed_when: False |
|
| 22 |
+ |
|
| 23 |
+ - name: Set ephemeral device if LABEL exists |
|
| 24 |
+ when: "ephemeral0.rc == 0" |
|
| 25 |
+ set_fact: |
|
| 26 |
+ ephemeral_device: "{{ ephemeral0.stdout }}"
|
|
| 27 |
+ |
|
| 28 |
+# If we have ephemeral storage and we don't appear to have setup swap, |
|
| 29 |
+# we will create a swap and move /opt to a large data partition there. |
|
| 30 |
+- include: ephemeral.yaml |
|
| 31 |
+ static: no |
|
| 32 |
+ when: |
|
| 33 |
+ - ephemeral_device is defined |
|
| 34 |
+ - ansible_memory_mb['swap']['total'] | int + 10 <= configure_swap_size |
|
| 35 |
+ |
|
| 36 |
+# If no ephemeral device and no swap, then we will setup some swap |
|
| 37 |
+# space on the root device to ensure all hosts a consistent memory |
|
| 38 |
+# environment. |
|
| 39 |
+- include: root.yaml |
|
| 40 |
+ static: no |
|
| 41 |
+ when: |
|
| 42 |
+ - ephemeral_device is undefined |
|
| 43 |
+ - ansible_memory_mb['swap']['total'] | int + 10 <= configure_swap_size |
|
| 44 |
+ |
|
| 45 |
+# ensure a standard level of swappiness. Some platforms |
|
| 46 |
+# (rax+centos7) come with swappiness of 0 (presumably because the |
|
| 47 |
+# vm doesn't come with swap setup ... but we just did that above), |
|
| 48 |
+# which depending on the kernel version can lead to the OOM killer |
|
| 49 |
+# kicking in on some processes despite swap being available; |
|
| 50 |
+# particularly things like mysql which have very high ratio of |
|
| 51 |
+# anonymous-memory to file-backed mappings. |
|
| 52 |
+# |
|
| 53 |
+# This sets swappiness low; we really don't want to be relying on |
|
| 54 |
+# cloud I/O based swap during our runs if we can help it |
|
| 55 |
+- name: Set swappiness |
|
| 56 |
+ become: yes |
|
| 57 |
+ sysctl: |
|
| 58 |
+ name: vm.swappiness |
|
| 59 |
+ value: 30 |
|
| 60 |
+ state: present |
|
| 61 |
+ |
|
| 62 |
+- debug: var=ephemeral_device |
| 0 | 63 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,63 @@ |
| 0 |
+# If no ephemeral devices are available, use root filesystem |
|
| 1 |
+ |
|
| 2 |
+- name: Calculate required swap |
|
| 3 |
+ set_fact: |
|
| 4 |
+ swap_required: "{{ configure_swap_size - ansible_memory_mb['swap']['total'] | int }}"
|
|
| 5 |
+ |
|
| 6 |
+- block: |
|
| 7 |
+ - name: Get root filesystem |
|
| 8 |
+ shell: df --output='fstype' /root | tail -1 |
|
| 9 |
+ register: root_fs |
|
| 10 |
+ |
|
| 11 |
+ - name: Save root filesystem |
|
| 12 |
+ set_fact: |
|
| 13 |
+ root_filesystem: "{{ root_fs.stdout }}"
|
|
| 14 |
+ |
|
| 15 |
+ - debug: var=root_filesystem |
|
| 16 |
+ |
|
| 17 |
+# Note, we don't use a sparse device to avoid wedging when disk space |
|
| 18 |
+# and memory are both unavailable. |
|
| 19 |
+ |
|
| 20 |
+# Cannot fallocate on filesystems like XFS, so use slower dd |
|
| 21 |
+- name: Create swap backing file for non-EXT fs |
|
| 22 |
+ when: '"ext" not in root_filesystem' |
|
| 23 |
+ become: yes |
|
| 24 |
+ command: dd if=/dev/zero of=/root/swapfile bs=1M count={{ swap_required }}
|
|
| 25 |
+ args: |
|
| 26 |
+ creates: /root/swapfile |
|
| 27 |
+ |
|
| 28 |
+- name: Create sparse swap backing file for EXT fs |
|
| 29 |
+ when: '"ext" in root_filesystem' |
|
| 30 |
+ become: yes |
|
| 31 |
+ command: fallocate -l {{ swap_required }}M /root/swapfile
|
|
| 32 |
+ args: |
|
| 33 |
+ creates: /root/swapfile |
|
| 34 |
+ |
|
| 35 |
+- name: Ensure swapfile perms |
|
| 36 |
+ become: yes |
|
| 37 |
+ file: |
|
| 38 |
+ path: /root/swapfile |
|
| 39 |
+ owner: root |
|
| 40 |
+ group: root |
|
| 41 |
+ mode: 0600 |
|
| 42 |
+ |
|
| 43 |
+- name: Make swapfile |
|
| 44 |
+ become: yes |
|
| 45 |
+ command: mkswap /root/swapfile |
|
| 46 |
+ |
|
| 47 |
+- name: Write swap to fstab |
|
| 48 |
+ become: yes |
|
| 49 |
+ mount: |
|
| 50 |
+ path: none |
|
| 51 |
+ src: /root/swapfile |
|
| 52 |
+ fstype: swap |
|
| 53 |
+ opts: sw |
|
| 54 |
+ passno: 0 |
|
| 55 |
+ dump: 0 |
|
| 56 |
+ state: present |
|
| 57 |
+ |
|
| 58 |
+- name: Add all swap |
|
| 59 |
+ become: yes |
|
| 60 |
+ command: swapon -a |
|
| 61 |
+ |
|
| 62 |
+- debug: var=swap_required |
| 0 | 63 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,15 @@ |
| 0 |
+Export journal files from devstack services |
|
| 1 |
+ |
|
| 2 |
+Export the systemd journal for every devstack service in native |
|
| 3 |
+journal format as well as text. Also, export a syslog-style file with |
|
| 4 |
+kernal and sudo messages. |
|
| 5 |
+ |
|
| 6 |
+Writes the output to the ``logs/`` subdirectory of |
|
| 7 |
+``devstack_base_dir``. |
|
| 8 |
+ |
|
| 9 |
+**Role Variables** |
|
| 10 |
+ |
|
| 11 |
+.. zuul:rolevar:: devstack_base_dir |
|
| 12 |
+ :default: /opt/stack |
|
| 13 |
+ |
|
| 14 |
+ The devstack base directory. |
| 0 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,29 @@ |
| 0 |
+# TODO: convert this to ansible |
|
| 1 |
+- name: Export journal files |
|
| 2 |
+ become: true |
|
| 3 |
+ shell: |
|
| 4 |
+ cmd: | |
|
| 5 |
+ u="" |
|
| 6 |
+ name="" |
|
| 7 |
+ for u in `systemctl list-unit-files | grep devstack | awk '{print $1}'`; do
|
|
| 8 |
+ name=$(echo $u | sed 's/devstack@/screen-/' | sed 's/\.service//') |
|
| 9 |
+ journalctl -o short-precise --unit $u | tee {{ devstack_base_dir }}/logs/$name.txt > /dev/null
|
|
| 10 |
+ done |
|
| 11 |
+ |
|
| 12 |
+ # Export the journal in export format to make it downloadable |
|
| 13 |
+ # for later searching. It can then be rewritten to a journal native |
|
| 14 |
+ # format locally using systemd-journal-remote. This makes a class of |
|
| 15 |
+ # debugging much easier. We don't do the native conversion here as |
|
| 16 |
+ # some distros do not package that tooling. |
|
| 17 |
+ journalctl -u 'devstack@*' -o export | \ |
|
| 18 |
+ xz --threads=0 - > {{ devstack_base_dir }}/logs/devstack.journal.xz
|
|
| 19 |
+ |
|
| 20 |
+ # The journal contains everything running under systemd, we'll |
|
| 21 |
+ # build an old school version of the syslog with just the |
|
| 22 |
+ # kernel and sudo messages. |
|
| 23 |
+ journalctl \ |
|
| 24 |
+ -t kernel \ |
|
| 25 |
+ -t sudo \ |
|
| 26 |
+ --no-pager \ |
|
| 27 |
+ --since="$(cat {{ devstack_base_dir }}/log-start-timestamp.txt)" \
|
|
| 28 |
+ | tee {{ devstack_base_dir }}/logs/syslog.txt > /dev/null
|
| 0 | 6 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,15 @@ |
| 0 |
+Set up the devstack cache directory |
|
| 1 |
+ |
|
| 2 |
+If the node has a cache of devstack image files, copy it into place. |
|
| 3 |
+ |
|
| 4 |
+**Role Variables** |
|
| 5 |
+ |
|
| 6 |
+.. zuul:rolevar:: devstack_base_dir |
|
| 7 |
+ :default: /opt/stack |
|
| 8 |
+ |
|
| 9 |
+ The devstack base directory. |
|
| 10 |
+ |
|
| 11 |
+.. zuul:rolevar:: devstack_cache_dir |
|
| 12 |
+ :default: /opt/cache |
|
| 13 |
+ |
|
| 14 |
+ The directory with the cached files. |
| 0 | 2 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,14 @@ |
| 0 |
+- name: Copy cached devstack files |
|
| 1 |
+ # This uses hard links to avoid using extra space. |
|
| 2 |
+ command: "find {{ devstack_cache_dir }}/files -mindepth 1 -maxdepth 1 -exec cp -l {} {{ devstack_base_dir }}/devstack/files/ ;"
|
|
| 3 |
+ become: true |
|
| 4 |
+ |
|
| 5 |
+- name: Set ownership of cached files |
|
| 6 |
+ file: |
|
| 7 |
+ path: '{{ devstack_base_dir }}/devstack/files'
|
|
| 8 |
+ state: directory |
|
| 9 |
+ recurse: true |
|
| 10 |
+ owner: stack |
|
| 11 |
+ group: stack |
|
| 12 |
+ mode: a+r |
|
| 13 |
+ become: yes |
| 0 | 14 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,11 @@ |
| 0 |
+Set up the devstack log directory |
|
| 1 |
+ |
|
| 2 |
+Create a log directory on the ephemeral disk partition to save space |
|
| 3 |
+on the root device. |
|
| 4 |
+ |
|
| 5 |
+**Role Variables** |
|
| 6 |
+ |
|
| 7 |
+.. zuul:rolevar:: devstack_base_dir |
|
| 8 |
+ :default: /opt/stack |
|
| 9 |
+ |
|
| 10 |
+ The devstack base directory. |
| 0 | 5 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,11 @@ |
| 0 |
+Set up the devstack source directories |
|
| 1 |
+ |
|
| 2 |
+Ensure that the base directory exists, and then move the source repos |
|
| 3 |
+into it. |
|
| 4 |
+ |
|
| 5 |
+**Role Variables** |
|
| 6 |
+ |
|
| 7 |
+.. zuul:rolevar:: devstack_base_dir |
|
| 8 |
+ :default: /opt/stack |
|
| 9 |
+ |
|
| 10 |
+ The devstack base directory. |
| 0 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,22 @@ |
| 0 |
+- name: Find all source repos used by this job |
|
| 1 |
+ find: |
|
| 2 |
+ paths: |
|
| 3 |
+ - src/git.openstack.org/openstack |
|
| 4 |
+ - src/git.openstack.org/openstack-dev |
|
| 5 |
+ - src/git.openstack.org/openstack-infra |
|
| 6 |
+ file_type: directory |
|
| 7 |
+ register: found_repos |
|
| 8 |
+ |
|
| 9 |
+- name: Copy Zuul repos into devstack working directory |
|
| 10 |
+ command: rsync -a {{ item.path }} {{ devstack_base_dir }}
|
|
| 11 |
+ with_items: '{{ found_repos.files }}'
|
|
| 12 |
+ become: yes |
|
| 13 |
+ |
|
| 14 |
+- name: Set ownership of repos |
|
| 15 |
+ file: |
|
| 16 |
+ path: '{{ devstack_base_dir }}'
|
|
| 17 |
+ state: directory |
|
| 18 |
+ recurse: true |
|
| 19 |
+ owner: stack |
|
| 20 |
+ group: stack |
|
| 21 |
+ become: yes |
| 0 | 22 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,16 @@ |
| 0 |
+Set up the `stack` user |
|
| 1 |
+ |
|
| 2 |
+Create the stack user, set up its home directory, and allow it to |
|
| 3 |
+sudo. |
|
| 4 |
+ |
|
| 5 |
+**Role Variables** |
|
| 6 |
+ |
|
| 7 |
+.. zuul:rolevar:: devstack_base_dir |
|
| 8 |
+ :default: /opt/stack |
|
| 9 |
+ |
|
| 10 |
+ The devstack base directory. |
|
| 11 |
+ |
|
| 12 |
+.. zuul:rolevar:: devstack_stack_home_dir |
|
| 13 |
+ :default: {{ devstack_base_dir }}
|
|
| 14 |
+ |
|
| 15 |
+ The home directory for the stack user. |
| 0 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,45 @@ |
| 0 |
+- name: Create stack group |
|
| 1 |
+ group: |
|
| 2 |
+ name: stack |
|
| 3 |
+ become: yes |
|
| 4 |
+ |
|
| 5 |
+# NOTE(andreaf) Create a user home_dir is not safe via |
|
| 6 |
+# the user module since it will fail if the containing |
|
| 7 |
+# folder does not exists. If the folder does exists and |
|
| 8 |
+# it's empty, the skeleton is setup and ownership set. |
|
| 9 |
+- name: Create the stack user home folder |
|
| 10 |
+ file: |
|
| 11 |
+ path: '{{ devstack_stack_home_dir }}'
|
|
| 12 |
+ state: directory |
|
| 13 |
+ become: yes |
|
| 14 |
+ |
|
| 15 |
+- name: Create stack user |
|
| 16 |
+ user: |
|
| 17 |
+ name: stack |
|
| 18 |
+ shell: /bin/bash |
|
| 19 |
+ home: '{{ devstack_stack_home_dir }}'
|
|
| 20 |
+ group: stack |
|
| 21 |
+ become: yes |
|
| 22 |
+ |
|
| 23 |
+- name: Set stack user home directory permissions |
|
| 24 |
+ file: |
|
| 25 |
+ path: '{{ devstack_stack_home_dir }}'
|
|
| 26 |
+ mode: 0755 |
|
| 27 |
+ become: yes |
|
| 28 |
+ |
|
| 29 |
+- name: Copy 50_stack_sh file to /etc/sudoers.d |
|
| 30 |
+ copy: |
|
| 31 |
+ src: 50_stack_sh |
|
| 32 |
+ dest: /etc/sudoers.d |
|
| 33 |
+ mode: 0440 |
|
| 34 |
+ owner: root |
|
| 35 |
+ group: root |
|
| 36 |
+ become: yes |
|
| 37 |
+ |
|
| 38 |
+- name: Create new/.cache folder within BASE |
|
| 39 |
+ file: |
|
| 40 |
+ path: '{{ devstack_stack_home_dir }}/.cache'
|
|
| 41 |
+ state: directory |
|
| 42 |
+ owner: stack |
|
| 43 |
+ group: stack |
|
| 44 |
+ become: yes |
| 0 | 3 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,20 @@ |
| 0 |
+- name: Create tempest group |
|
| 1 |
+ group: |
|
| 2 |
+ name: tempest |
|
| 3 |
+ become: yes |
|
| 4 |
+ |
|
| 5 |
+- name: Create tempest user |
|
| 6 |
+ user: |
|
| 7 |
+ name: tempest |
|
| 8 |
+ shell: /bin/bash |
|
| 9 |
+ group: tempest |
|
| 10 |
+ become: yes |
|
| 11 |
+ |
|
| 12 |
+- name: Copy 51_tempest_sh to /etc/sudoers.d |
|
| 13 |
+ copy: |
|
| 14 |
+ src: 51_tempest_sh |
|
| 15 |
+ dest: /etc/sudoers.d |
|
| 16 |
+ owner: root |
|
| 17 |
+ group: root |
|
| 18 |
+ mode: 0440 |
|
| 19 |
+ become: yes |
| 0 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,56 @@ |
| 0 |
+- name: Check for /bin/journalctl file |
|
| 1 |
+ command: which journalctl |
|
| 2 |
+ changed_when: False |
|
| 3 |
+ failed_when: False |
|
| 4 |
+ register: which_out |
|
| 5 |
+ |
|
| 6 |
+- block: |
|
| 7 |
+ - name: Get current date |
|
| 8 |
+ command: date +"%Y-%m-%d %H:%M:%S" |
|
| 9 |
+ register: date_out |
|
| 10 |
+ |
|
| 11 |
+ - name: Copy current date to log-start-timestamp.txt |
|
| 12 |
+ copy: |
|
| 13 |
+ dest: "{{ devstack_base_dir }}/log-start-timestamp.txt"
|
|
| 14 |
+ content: "{{ date_out.stdout }}"
|
|
| 15 |
+ when: which_out.rc == 0 |
|
| 16 |
+ become: yes |
|
| 17 |
+ |
|
| 18 |
+- block: |
|
| 19 |
+ - name: Stop rsyslog |
|
| 20 |
+ service: name=rsyslog state=stopped |
|
| 21 |
+ |
|
| 22 |
+ - name: Save syslog file prior to devstack run |
|
| 23 |
+ command: mv /var/log/syslog /var/log/syslog-pre-devstack |
|
| 24 |
+ |
|
| 25 |
+ - name: Save kern.log file prior to devstack run |
|
| 26 |
+ command: mv /var/log/kern.log /var/log/kern_log-pre-devstack |
|
| 27 |
+ |
|
| 28 |
+ - name: Recreate syslog file |
|
| 29 |
+ file: name=/var/log/syslog state=touch |
|
| 30 |
+ |
|
| 31 |
+ - name: Recreate syslog file owner and group |
|
| 32 |
+ command: chown /var/log/syslog --ref /var/log/syslog-pre-devstack |
|
| 33 |
+ |
|
| 34 |
+ - name: Recreate syslog file permissions |
|
| 35 |
+ command: chmod /var/log/syslog --ref /var/log/syslog-pre-devstack |
|
| 36 |
+ |
|
| 37 |
+ - name: Add read permissions to all on syslog file |
|
| 38 |
+ file: name=/var/log/syslog mode=a+r |
|
| 39 |
+ |
|
| 40 |
+ - name: Recreate kern.log file |
|
| 41 |
+ file: name=/var/log/kern.log state=touch |
|
| 42 |
+ |
|
| 43 |
+ - name: Recreate kern.log file owner and group |
|
| 44 |
+ command: chown /var/log/kern.log --ref /var/log/kern_log-pre-devstack |
|
| 45 |
+ |
|
| 46 |
+ - name: Recreate kern.log file permissions |
|
| 47 |
+ command: chmod /var/log/kern.log --ref /var/log/kern_log-pre-devstack |
|
| 48 |
+ |
|
| 49 |
+ - name: Add read permissions to all on kern.log file |
|
| 50 |
+ file: name=/var/log/kern.log mode=a+r |
|
| 51 |
+ |
|
| 52 |
+ - name: Start rsyslog |
|
| 53 |
+ service: name=rsyslog state=started |
|
| 54 |
+ when: which_out.rc == 1 |
|
| 55 |
+ become: yes |
| 0 | 56 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,63 @@ |
| 0 |
+Write the local.conf file for use by devstack |
|
| 1 |
+ |
|
| 2 |
+**Role Variables** |
|
| 3 |
+ |
|
| 4 |
+.. zuul:rolevar:: devstack_base_dir |
|
| 5 |
+ :default: /opt/stack |
|
| 6 |
+ |
|
| 7 |
+ The devstack base directory. |
|
| 8 |
+ |
|
| 9 |
+.. zuul:rolevar:: devstack_local_conf_path |
|
| 10 |
+ :default: {{ devstack_base_dir }}/devstack/local.conf
|
|
| 11 |
+ |
|
| 12 |
+ The path of the local.conf file. |
|
| 13 |
+ |
|
| 14 |
+.. zuul:rolevar:: devstack_localrc |
|
| 15 |
+ :type: dict |
|
| 16 |
+ |
|
| 17 |
+ A dictionary of variables that should be written to the localrc |
|
| 18 |
+ section of local.conf. The values (which are strings) may contain |
|
| 19 |
+ bash shell variables, and will be ordered so that variables used by |
|
| 20 |
+ later entries appear first. |
|
| 21 |
+ |
|
| 22 |
+.. zuul:rolevar:: devstack_local_conf |
|
| 23 |
+ :type: dict |
|
| 24 |
+ |
|
| 25 |
+ A complex argument consisting of nested dictionaries which combine |
|
| 26 |
+ to form the meta-sections of the local_conf file. The top level is |
|
| 27 |
+ a dictionary of phases, followed by dictionaries of filenames, then |
|
| 28 |
+ sections, which finally contain key-value pairs for the INI file |
|
| 29 |
+ entries in those sections. |
|
| 30 |
+ |
|
| 31 |
+ The keys in this dictionary are the devstack phases. |
|
| 32 |
+ |
|
| 33 |
+ .. zuul:rolevar:: [phase] |
|
| 34 |
+ :type: dict |
|
| 35 |
+ |
|
| 36 |
+ The keys in this dictionary are the filenames for this phase. |
|
| 37 |
+ |
|
| 38 |
+ .. zuul:rolevar:: [filename] |
|
| 39 |
+ :type: dict |
|
| 40 |
+ |
|
| 41 |
+ The keys in this dictionary are the INI sections in this file. |
|
| 42 |
+ |
|
| 43 |
+ .. zuul:rolevar:: [section] |
|
| 44 |
+ :type: dict |
|
| 45 |
+ |
|
| 46 |
+ This is a dictionary of key-value pairs which comprise |
|
| 47 |
+ this section of the INI file. |
|
| 48 |
+ |
|
| 49 |
+.. zuul:rolevar:: devstack_services |
|
| 50 |
+ :type: dict |
|
| 51 |
+ |
|
| 52 |
+ A dictionary mapping service names to boolean values. If the |
|
| 53 |
+ boolean value is ``false``, a ``disable_service`` line will be |
|
| 54 |
+ emitted for the service name. If it is ``true``, then |
|
| 55 |
+ ``enable_service`` will be emitted. All other values are ignored. |
|
| 56 |
+ |
|
| 57 |
+.. zuul:rolevar:: devstack_plugins |
|
| 58 |
+ :type: dict |
|
| 59 |
+ |
|
| 60 |
+ A dictionary mapping a plugin name to a git repo location. If the |
|
| 61 |
+ location is a non-empty string, then an ``enable_plugin`` line will |
|
| 62 |
+ be emmitted for the plugin name. |
| 0 | 2 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,185 @@ |
| 0 |
+# Copyright (C) 2017 Red Hat, Inc. |
|
| 1 |
+# |
|
| 2 |
+# Licensed under the Apache License, Version 2.0 (the "License"); |
|
| 3 |
+# you may not use this file except in compliance with the License. |
|
| 4 |
+# You may obtain a copy of the License at |
|
| 5 |
+# |
|
| 6 |
+# http://www.apache.org/licenses/LICENSE-2.0 |
|
| 7 |
+# |
|
| 8 |
+# Unless required by applicable law or agreed to in writing, software |
|
| 9 |
+# distributed under the License is distributed on an "AS IS" BASIS, |
|
| 10 |
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
|
| 11 |
+# implied. |
|
| 12 |
+# |
|
| 13 |
+# See the License for the specific language governing permissions and |
|
| 14 |
+# limitations under the License. |
|
| 15 |
+ |
|
| 16 |
+import re |
|
| 17 |
+ |
|
| 18 |
+ |
|
| 19 |
+class VarGraph(object): |
|
| 20 |
+ # This is based on the JobGraph from Zuul. |
|
| 21 |
+ |
|
| 22 |
+ def __init__(self, vars): |
|
| 23 |
+ self.vars = {}
|
|
| 24 |
+ self._varnames = set() |
|
| 25 |
+ self._dependencies = {} # dependent_var_name -> set(parent_var_names)
|
|
| 26 |
+ for k, v in vars.items(): |
|
| 27 |
+ self._varnames.add(k) |
|
| 28 |
+ for k, v in vars.items(): |
|
| 29 |
+ self._addVar(k, str(v)) |
|
| 30 |
+ |
|
| 31 |
+ bash_var_re = re.compile(r'\$\{?(\w+)')
|
|
| 32 |
+ def getDependencies(self, value): |
|
| 33 |
+ return self.bash_var_re.findall(value) |
|
| 34 |
+ |
|
| 35 |
+ def _addVar(self, key, value): |
|
| 36 |
+ if key in self.vars: |
|
| 37 |
+ raise Exception("Variable {} already added".format(key))
|
|
| 38 |
+ self.vars[key] = value |
|
| 39 |
+ # Append the dependency information |
|
| 40 |
+ self._dependencies.setdefault(key, set()) |
|
| 41 |
+ try: |
|
| 42 |
+ for dependency in self.getDependencies(value): |
|
| 43 |
+ if dependency == key: |
|
| 44 |
+ # A variable is allowed to reference itself; no |
|
| 45 |
+ # dependency link needed in that case. |
|
| 46 |
+ continue |
|
| 47 |
+ if dependency not in self._varnames: |
|
| 48 |
+ # It's not necessary to create a link for an |
|
| 49 |
+ # external variable. |
|
| 50 |
+ continue |
|
| 51 |
+ # Make sure a circular dependency is never created |
|
| 52 |
+ ancestor_vars = self._getParentVarNamesRecursively( |
|
| 53 |
+ dependency, soft=True) |
|
| 54 |
+ ancestor_vars.add(dependency) |
|
| 55 |
+ if any((key == anc_var) for anc_var in ancestor_vars): |
|
| 56 |
+ raise Exception("Dependency cycle detected in var {}".
|
|
| 57 |
+ format(key)) |
|
| 58 |
+ self._dependencies[key].add(dependency) |
|
| 59 |
+ except Exception: |
|
| 60 |
+ del self.vars[key] |
|
| 61 |
+ del self._dependencies[key] |
|
| 62 |
+ raise |
|
| 63 |
+ |
|
| 64 |
+ def getVars(self): |
|
| 65 |
+ ret = [] |
|
| 66 |
+ keys = sorted(self.vars.keys()) |
|
| 67 |
+ seen = set() |
|
| 68 |
+ for key in keys: |
|
| 69 |
+ dependencies = self.getDependentVarsRecursively(key) |
|
| 70 |
+ for var in dependencies + [key]: |
|
| 71 |
+ if var not in seen: |
|
| 72 |
+ ret.append((var, self.vars[var])) |
|
| 73 |
+ seen.add(var) |
|
| 74 |
+ return ret |
|
| 75 |
+ |
|
| 76 |
+ def getDependentVarsRecursively(self, parent_var): |
|
| 77 |
+ dependent_vars = [] |
|
| 78 |
+ |
|
| 79 |
+ current_dependent_vars = self._dependencies[parent_var] |
|
| 80 |
+ for current_var in current_dependent_vars: |
|
| 81 |
+ if current_var not in dependent_vars: |
|
| 82 |
+ dependent_vars.append(current_var) |
|
| 83 |
+ for dep in self.getDependentVarsRecursively(current_var): |
|
| 84 |
+ if dep not in dependent_vars: |
|
| 85 |
+ dependent_vars.append(dep) |
|
| 86 |
+ return dependent_vars |
|
| 87 |
+ |
|
| 88 |
+ def _getParentVarNamesRecursively(self, dependent_var, soft=False): |
|
| 89 |
+ all_parent_vars = set() |
|
| 90 |
+ vars_to_iterate = set([dependent_var]) |
|
| 91 |
+ while len(vars_to_iterate) > 0: |
|
| 92 |
+ current_var = vars_to_iterate.pop() |
|
| 93 |
+ current_parent_vars = self._dependencies.get(current_var) |
|
| 94 |
+ if current_parent_vars is None: |
|
| 95 |
+ if soft: |
|
| 96 |
+ current_parent_vars = set() |
|
| 97 |
+ else: |
|
| 98 |
+ raise Exception("Dependent var {} not found: ".format(
|
|
| 99 |
+ dependent_var)) |
|
| 100 |
+ new_parent_vars = current_parent_vars - all_parent_vars |
|
| 101 |
+ vars_to_iterate |= new_parent_vars |
|
| 102 |
+ all_parent_vars |= new_parent_vars |
|
| 103 |
+ return all_parent_vars |
|
| 104 |
+ |
|
| 105 |
+ |
|
| 106 |
+class LocalConf(object): |
|
| 107 |
+ |
|
| 108 |
+ def __init__(self, localrc, localconf, services, plugins): |
|
| 109 |
+ self.localrc = [] |
|
| 110 |
+ self.meta_sections = {}
|
|
| 111 |
+ if plugins: |
|
| 112 |
+ self.handle_plugins(plugins) |
|
| 113 |
+ if services: |
|
| 114 |
+ self.handle_services(services) |
|
| 115 |
+ if localrc: |
|
| 116 |
+ self.handle_localrc(localrc) |
|
| 117 |
+ if localconf: |
|
| 118 |
+ self.handle_localconf(localconf) |
|
| 119 |
+ |
|
| 120 |
+ def handle_plugins(self, plugins): |
|
| 121 |
+ for k, v in plugins.items(): |
|
| 122 |
+ if v: |
|
| 123 |
+ self.localrc.append('enable_plugin {} {}'.format(k, v))
|
|
| 124 |
+ |
|
| 125 |
+ def handle_services(self, services): |
|
| 126 |
+ for k, v in services.items(): |
|
| 127 |
+ if v is False: |
|
| 128 |
+ self.localrc.append('disable_service {}'.format(k))
|
|
| 129 |
+ elif v is True: |
|
| 130 |
+ self.localrc.append('enable_service {}'.format(k))
|
|
| 131 |
+ |
|
| 132 |
+ def handle_localrc(self, localrc): |
|
| 133 |
+ vg = VarGraph(localrc) |
|
| 134 |
+ for k, v in vg.getVars(): |
|
| 135 |
+ self.localrc.append('{}={}'.format(k, v))
|
|
| 136 |
+ |
|
| 137 |
+ def handle_localconf(self, localconf): |
|
| 138 |
+ for phase, phase_data in localconf.items(): |
|
| 139 |
+ for fn, fn_data in phase_data.items(): |
|
| 140 |
+ ms_name = '[[{}|{}]]'.format(phase, fn)
|
|
| 141 |
+ ms_data = [] |
|
| 142 |
+ for section, section_data in fn_data.items(): |
|
| 143 |
+ ms_data.append('[{}]'.format(section))
|
|
| 144 |
+ for k, v in section_data.items(): |
|
| 145 |
+ ms_data.append('{} = {}'.format(k, v))
|
|
| 146 |
+ ms_data.append('')
|
|
| 147 |
+ self.meta_sections[ms_name] = ms_data |
|
| 148 |
+ |
|
| 149 |
+ def write(self, path): |
|
| 150 |
+ with open(path, 'w') as f: |
|
| 151 |
+ f.write('[[local|localrc]]\n')
|
|
| 152 |
+ f.write('\n'.join(self.localrc))
|
|
| 153 |
+ f.write('\n\n')
|
|
| 154 |
+ for section, lines in self.meta_sections.items(): |
|
| 155 |
+ f.write('{}\n'.format(section))
|
|
| 156 |
+ f.write('\n'.join(lines))
|
|
| 157 |
+ |
|
| 158 |
+ |
|
| 159 |
+def main(): |
|
| 160 |
+ module = AnsibleModule( |
|
| 161 |
+ argument_spec=dict( |
|
| 162 |
+ plugins=dict(type='dict'), |
|
| 163 |
+ services=dict(type='dict'), |
|
| 164 |
+ localrc=dict(type='dict'), |
|
| 165 |
+ local_conf=dict(type='dict'), |
|
| 166 |
+ path=dict(type='str'), |
|
| 167 |
+ ) |
|
| 168 |
+ ) |
|
| 169 |
+ |
|
| 170 |
+ p = module.params |
|
| 171 |
+ lc = LocalConf(p.get('localrc'),
|
|
| 172 |
+ p.get('local_conf'),
|
|
| 173 |
+ p.get('services'),
|
|
| 174 |
+ p.get('plugins'))
|
|
| 175 |
+ lc.write(p['path']) |
|
| 176 |
+ |
|
| 177 |
+ module.exit_json() |
|
| 178 |
+ |
|
| 179 |
+ |
|
| 180 |
+from ansible.module_utils.basic import * # noqa |
|
| 181 |
+from ansible.module_utils.basic import AnsibleModule |
|
| 182 |
+ |
|
| 183 |
+if __name__ == '__main__': |
|
| 184 |
+ main() |
| 0 | 185 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,9 @@ |
| 0 |
+- name: Write a job-specific local_conf file |
|
| 1 |
+ become: true |
|
| 2 |
+ become_user: stack |
|
| 3 |
+ devstack_local_conf: |
|
| 4 |
+ path: "{{ devstack_local_conf_path }}"
|
|
| 5 |
+ plugins: "{{ devstack_plugins|default(omit) }}"
|
|
| 6 |
+ services: "{{ devstack_services|default(omit) }}"
|
|
| 7 |
+ localrc: "{{ devstack_localrc|default(omit) }}"
|
|
| 8 |
+ local_conf: "{{ devstack_local_conf|default(omit) }}"
|