Introduce tripleo-container-manage role

This is a first ieration of the role, but there is still a long TODO,
that will come later in separated patches:
- Add molecule testing
- In podman.yaml, add cpuset_cpus with parity of what is in paunch
- Remove containers that are:
  - managed by tripleo-ansible (using the container_label flag)
  - not in the container-startup-config
- Print stdout when containers start as it was done with paunch

Story: 2006732
Task: 37165

Co-Authored-By: Kevin Carter <kecarter@redhat.com>
Co-Authored-By: Alex Schultz <aschultz@redhat.com>

Change-Id: I2f88caa8e1c230dfe846a8a0dd9f939b98992cd5
This commit is contained in:
Emilien Macchi 2019-10-02 12:01:28 -04:00
parent 231aa5316d
commit a191a2d600
13 changed files with 533 additions and 0 deletions

View File

@ -0,0 +1,6 @@
===============================
Role - tripleo-container-manage
===============================
.. ansibleautoplugin::
:role: tripleo_ansible/roles/tripleo-container-manage

View File

@ -0,0 +1,21 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# 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.
# All variables intended for modification should place placed in this file.
# All variables within this role should have a prefix of "tripleo_container_manage"
tripleo_container_cli: podman

View File

@ -0,0 +1,55 @@
#!/usr/bin/env python
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# 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.
from collections import OrderedDict
from operator import itemgetter
class FilterModule(object):
def filters(self):
return {
'singledict': self.singledict,
'subsort': self.subsort
}
def subsort(self, dict_to_sort, attribute, null_value=None):
"""Sort a hash from a sub-element.
This filter will return a sorted list of tuples from a dictionary
using an attribute from within the hash. If the sort attribute is
undefined it will be set in the returned item as the defined
`null_value`. This makes it possible to sort all items equally.
"""
for k, v in dict_to_sort.items():
if attribute not in v:
dict_to_sort[k][attribute] = null_value
return sorted(
dict_to_sort.items(),
key=lambda x: x[1][attribute]
)
def singledict(self, list_to_convert):
"""Generate a single dictionary from a list of dictionaries.
This filter will return a single dictionary from a list of
dictionaries.
"""
return_dict = {}
for i in list_to_convert:
return_dict.update(i)
return return_dict

View File

@ -0,0 +1,44 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# 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.
galaxy_info:
author: OpenStack
description: TripleO OpenStack Role -- tripleo-container-manage
company: Red Hat
license: Apache-2.0
min_ansible_version: 2.7
#
# Provide a list of supported platforms, and for each platform a list of versions.
# If you don't wish to enumerate all versions for a particular platform, use 'all'.
# To view available platforms and versions (or releases), visit:
# https://galaxy.ansible.com/api/v1/platforms/
#
platforms:
- name: Fedora
versions:
- 28
- name: CentOS
versions:
- 7
galaxy_tags:
- tripleo
# List your role dependencies here, one per line. Be sure to remove the '[]' above,
# if you add dependencies to this list.
dependencies: []

View File

@ -0,0 +1,37 @@
# Molecule managed
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# 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.
{% if item.registry is defined %}
FROM {{ item.registry.url }}/{{ item.image }}
{% else %}
FROM {{ item.image }}
{% endif %}
RUN if [ $(command -v apt-get) ]; then apt-get update && apt-get install -y python sudo bash ca-certificates && apt-get clean; \
elif [ $(command -v dnf) ]; then dnf makecache && dnf --assumeyes install python sudo python-devel python*-dnf bash {{ item.pkg_extras | default('') }} && dnf clean all; \
elif [ $(command -v yum) ]; then yum makecache fast && yum install -y python sudo yum-plugin-ovl python-setuptools bash {{ item.pkg_extras | default('') }} && sed -i 's/plugins=0/plugins=1/g' /etc/yum.conf && yum clean all; \
elif [ $(command -v zypper) ]; then zypper refresh && zypper install -y python sudo bash python-xml {{ item.pkg_extras | default('') }} && zypper clean -a; \
elif [ $(command -v apk) ]; then apk update && apk add --no-cache python sudo bash ca-certificates {{ item.pkg_extras | default('') }}; \
elif [ $(command -v xbps-install) ]; then xbps-install -Syu && xbps-install -y python sudo bash ca-certificates {{ item.pkg_extras | default('') }} && xbps-remove -O; fi
{% for pkg in item.easy_install | default([]) %}
# install pip for centos where there is no python-pip rpm in default repos
RUN easy_install {{ pkg }}
{% endfor %}
CMD ["sh", "-c", "while true; do sleep 10000; done"]

View File

@ -0,0 +1,48 @@
---
driver:
name: docker
log: true
platforms:
- name: centos7
hostname: centos7
image: centos:7
dockerfile: Dockerfile
pkg_extras: python-setuptools
easy_install:
- pip
environment: &env
http_proxy: "{{ lookup('env', 'http_proxy') }}"
https_proxy: "{{ lookup('env', 'https_proxy') }}"
- name: fedora28
hostname: fedora28
image: fedora:28
dockerfile: Dockerfile
pkg_extras: python*-setuptools
environment:
<<: *env
provisioner:
name: ansible
log: true
env:
ANSIBLE_STDOUT_CALLBACK: yaml
scenario:
test_sequence:
- destroy
- create
- prepare
- converge
- verify
- destroy
lint:
enabled: false
verifier:
name: testinfra
lint:
name: flake8

View File

@ -0,0 +1,21 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# 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.
- name: Converge
hosts: all
roles:
- role: "tripleo-container-manage"

View File

@ -0,0 +1,21 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# 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.
- name: Prepare
hosts: all
roles:
- role: test_deps

View File

@ -0,0 +1,76 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# 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.
# "tripleo-container-manage" will search for and load any operating system variable file
# found within the "vars/" path. If no OS files are found the task will skip.
- name: Gather variables for each operating system
include_vars: "{{ item }}"
with_first_found:
- skip: true
files:
- "{{ ansible_distribution | lower }}-{{ ansible_distribution_version | lower }}.yml"
- "{{ ansible_distribution | lower }}-{{ ansible_distribution_major_version | lower }}.yml"
- "{{ ansible_os_family | lower }}-{{ ansible_distribution_major_version | lower }}.yml"
- "{{ ansible_distribution | lower }}.yml"
- "{{ ansible_os_family | lower }}-{{ ansible_distribution_version.split('.')[0] }}.yml"
- "{{ ansible_os_family | lower }}.yml"
tags:
- always
- name: Create container logs path
file:
path: "{{ tripleo_container_manage_log_path }}"
state: directory
owner: root
group: root
become: true
when:
- tripleo_container_manage_log_path is defined
- name: Generate containers configs data
no_log: true
block:
- name: "Find all hashed configs configs for step {{ step }}"
find:
paths: "/var/lib/tripleo-config/container-startup-config/step_{{ step }}"
patterns: 'hashed-*.json'
register: hashed_files
- name: "Read config for each container at step {{ step }}"
slurp:
src: "{{ item.path }}"
register: containers_data
loop: "{{ hashed_files.files }}"
- set_fact:
container_hash: "{'{{ item.source|basename|regex_replace('^hashed-','')|regex_replace('.json$','')}}': {{item.content|b64decode|from_json}} }"
register: container_hashes
loop: "{{ containers_data['results'] }}"
- set_fact:
container_hash: "{{ item.ansible_facts.container_hash | combine(item.ansible_facts.container_hash) }}"
register: container_hashes
loop: "{{ container_hashes.results }}"
- set_fact:
all_containers_hash: "{{ container_hashes.results | map(attribute='ansible_facts.container_hash') | list | singledict() }}"
- name: Manage containers with Podman
when:
- tripleo_container_cli == 'podman'
become: true
block:
- name: "Manage Podman containers at step {{ step }}"
include_tasks: podman.yaml
loop: "{{ all_containers_hash | subsort(attribute='start_order', null_value=0) }}"

View File

@ -0,0 +1,164 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# 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.
- name: "Execute a command within a running container for {{ item.0 }}"
when:
- item.1.action is defined
- item.1.action == 'exec'
block:
- name: "Check if {{ item.1.command.0 }} container exists"
command: "{{ tripleo_container_cli }} container exists {{ item.1.command.0 }}"
- name: "Check if {{ item.1.command.0 }} container is running"
block:
- name: "Gather podman infos for {{ item.1.command.0 }}"
podman_container_info:
name: "{{ item.1.command.0 }}"
register: podman_containers
- name: "Fail if {{ item.1.command.0 }} is not running"
fail:
msg: "Can't run container exec for {{ item.0 }}, {{ item.1.command.0 }} is not running"
when:
- not podman_containers.ansible_facts.podman_containers.0.State.Running
- name: "Prepare the exec command for {{ item.0 }}"
set_fact:
cmd_template:
- "{{ tripleo_container_cli }}"
- "exec"
- "-u"
- "{{ item.1.user if item.1.user is defined else 'root' }}"
- name: "Run the container exec for {{ item.0 }}"
command:
argv: "{{ cmd_template + item.1.command }}"
- name: "Manage container for {{ item.0 }}"
podman_container:
cap_add: "{{ item.1.cap_add | default(omit) }}"
cap_drop: "{{ item.1.cap_drop | default(omit) }}"
command: "{{ item.1.command | default(omit) }}"
conmon_pidfile: "/var/run/{{ item.0 }}.pid"
cpu_shares: "{{ item.1.cpu_shares | default(omit) | int }}"
# cpuset_cpus: "{{ item.1.cpuset_cpus | default(omit) }}"
detach: "{{ item.1.detach | default(true) }}"
env: "{{ item.1.environment if item.1.environment is defined and item.1.environment else {} }}"
env_file: "{{ item.1.env_file | default(omit) }}"
etc_hosts: "{{ item.1.extra_hosts | default({}) }}"
group_add: "{{ item.1.group_add | default(omit) }}"
hostname: "{{ item.1.hostname | default(omit) }}"
image: "{{ item.1.image }}"
interactive: "{{ item.1.interactive | default(false) }}"
ipc: "{{ item.1.ipc | default(omit) }}"
label:
config_id: "tripleo_step{{ step }}"
container_name: "{{ item.0 }}"
managed_by: tripleo_ansible
config_data: "{{ item.1 | to_json }}"
log_driver: "{{ 'k8s-file' if tripleo_container_manage_log_path is defined else '' }}"
# log_opt: |
# "{{ 'path=' if tripleo_container_manage_log_path is defined else '' }}
# {{ tripleo_container_manage_log_path | default('') }}
# {{ '/' if tripleo_container_manage_log_path is defined else '' }}
# {{ item.0 if tripleo_container_manage_log_path is defined else '' }}"
memory: "{{ item.1.mem_limit | default(omit) }}"
memory_swap: "{{ item.1.mem_swappiness | default(omit) }}"
name: "{{ item.0 }}"
net: "{{ item.1.net | default('none') }}"
pid: "{{ item.1.pid | default(omit) }}"
privileged: "{{ item.1.privileged | default(false) }}"
rm: "{{ item.1.remove | default(false) }}"
security_opt: "{{ item.1.security_opt | default(omit) }}"
state: present
stop_signal: "{{ item.1.stop_signal | default(omit) }}"
stop_timeout: "{{ item.1.stop_grace_period | default(omit) | int }}"
tty: "{{ item.1.tty | default(false) }}"
ulimit: "{{ item.1.ulimit | default(omit) }}"
user: "{{ item.1.user | default(omit) }}"
uts: "{{ item.1.uts | default(omit) }}"
volume: "{{ item.1.volumes | default(omit) }}"
volumes_from: "{{ item.1.volumes_from | default([]) }}"
when:
- item.1.action is not defined
- name: Manage systemd service for {{ item.0 }}
when:
- item.1.action is not defined
- item.1.restart is defined
# systemd doesn't have the equivalent of docker unless-stopped.
# Let's force 'always' so containers aren't restarted when stopped by
# systemd, but restarted when in failure.
- item.1.restart == 'always' or item.1.restart == 'unless-stopped'
block:
- name: Check if /etc/sysconfig/podman_drop_in exists
stat:
path: /etc/sysconfig/podman_drop_in
register: podman_drop_in
- name: Set podman_drop_in fact
set_fact:
podman_drop_in: true
when:
- podman_drop_in.stat.exists
- name: "Start systemd service for {{ item.0 }}"
block:
- name: "Remove trailing .requires for {{ item.0 }}"
file:
path: "/etc/systemd/system/tripleo_{{ item.0 }}.requires"
state: absent
- name: "Create systemd unit file for {{ item.0 }} service"
template:
src: systemd-service.j2
dest: "/etc/systemd/system/tripleo_{{ item.0 }}.service"
mode: '0644'
owner: root
group: root
register: systemd_file
- name: "Enable and start systemd service for {{ item.0 }}"
systemd:
# Restart the service if it was already running
state: restarted
name: "tripleo_{{ item.0 }}.service"
enabled: true
daemon_reload: true
when:
- systemd_file.changed
- name: "Manage systemd healthcheck for {{ item.0 }}"
when:
- not (container_healthcheck_disabled | default(false))
- item.1.healthcheck is defined
block:
- name: "Create systemd unit file for {{ item.0 }} healthcheck"
template:
src: systemd-healthcheck.j2
dest: "/etc/systemd/system/tripleo_{{ item.0 }}_healthcheck.service"
mode: '0644'
owner: root
group: root
register: systemd_healthcheck
- name: "Create systemd timer for {{ item.0 }} healthcheck"
template:
src: systemd-timer.j2
dest: "/etc/systemd/system/tripleo_{{ item.0 }}_healthcheck.timer"
mode: '0644'
owner: root
group: root
register: systemd_timer
- name: "Enable and start systemd timer for {{ item.0 }}"
systemd:
# Restart the timer if it was already running
state: restarted
name: "tripleo_{{ item.0 }}_healthcheck.timer"
enabled: true
daemon_reload: true
when:
- systemd_healthcheck.changed or systemd_timer.changed

View File

@ -0,0 +1,9 @@
[Unit]
Description={{ item.0 }} healthcheck
After=paunch-container-shutdown.service tripleo_{{ item.0 }}.service
Requisite={{ item.0 }}.service
[Service]
Type=oneshot
ExecStart=/usr/bin/podman exec {{ item.0 }} {{ item.1.healthcheck.test }}
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,22 @@
[Unit]
Description={{ item.0 }} container
After=paunch-container-shutdown.service
Wants={{ item.1.depends_on | default([]) | join(',') }}
[Service]
Restart=always
{% if item.1.depends_on is defined and (item.1.depends_on | length > 0) and podman_drop_in | default('false') %}
ExecStart=/usr/libexec/paunch-start-podman-container {{ item.0 }}
{% else %}
ExecStart=/usr/bin/podman start {{ item.0 }}
{% endif %}
ExecStop=/usr/bin/podman stop -t {{ item.1.stop_grace_period | default(10) | int }} {{ item.0 }}
KillMode=none
Type=forking
PIDFile=/var/run/{{ item.0 }}.pid
{% if item.1.systemd_exec_flags is defined %}
{% for s_flag, s_value in item.1.systemd_exec_flags.items() %}
{{ s_flag }}={{ s_value }}
{% endfor %}
{% endif %}
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,9 @@
[Unit]
Description={{ item.0 }} container healthcheck
PartOf={{ item.0 }}.service
[Timer]
OnActiveSec=120
OnUnitActiveSec={{ item.1.check_interval | default(60) }}
RandomizedDelaySec={{ 45 if item.1.check_interval is not defined else (item.1.check_interval * 3 / 4) | int | abs }}
[Install]
WantedBy=timers.target