Utility to launch and manage containers using YAML based configuration data
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

253 lines
8.5 KiB

  1. # Copyright 2018 Red Hat, Inc.
  2. # All Rights Reserved.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  5. # not use this file except in compliance with the License. You may obtain
  6. # a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. # License for the specific language governing permissions and limitations
  14. # under the License.
  15. import os
  16. import shutil
  17. from paunch import constants
  18. from paunch.utils import common
  19. from paunch.utils import systemctl
  20. DROP_IN_MARKER_FILE = '/etc/sysconfig/podman_drop_in'
  21. def service_create(container, cconfig, sysdir=constants.SYSTEMD_DIR, log=None):
  22. """Create a service in systemd
  23. :param container: container name
  24. :type container: String
  25. :param cconfig: container configuration
  26. :type cconfig: Dictionary
  27. :param sysdir: systemd unit files directory
  28. :type sysdir: String
  29. :param log: optional pre-defined logger for messages
  30. :type log: logging.RootLogger
  31. """
  32. log = log or common.configure_logging(__name__)
  33. # We prefix the SystemD service so we can identify them better:
  34. # e.g. systemctl list-unit-files | grep tripleo_
  35. # It'll help to not conflict when rpms are installed on the host and
  36. # have the same service name as their container name.
  37. # For example haproxy rpm and haproxy container would have the same
  38. # service name so the prefix will help to not having this conflict
  39. # when removing the rpms during a cleanup by the operator.
  40. service = 'tripleo_' + container
  41. wants = " ".join(systemctl.format_name(str(x)) for x in
  42. cconfig.get('depends_on', []))
  43. restart = cconfig.get('restart', 'always')
  44. stop_grace_period = cconfig.get('stop_grace_period', '10')
  45. # Please refer to systemd.exec documentation for those entries
  46. # https://www.freedesktop.org/software/systemd/man/systemd.exec.html
  47. sys_exec = cconfig.get('systemd_exec_flags', {})
  48. # SystemD doesn't have the equivalent of docker unless-stopped.
  49. # Let's force 'always' so containers aren't restarted when stopped by
  50. # systemd, but restarted when in failure. Also this code is only for
  51. # podman now, so nothing changed for Docker deployments.
  52. if restart == 'unless-stopped':
  53. restart = 'always'
  54. # If the service depends on other services, it must be stopped
  55. # in a specific order. The host can be configured to prevent
  56. # systemd from stopping the associated systemd scopes too early,
  57. # so make sure to generate the start command accordingly.
  58. if (len(cconfig.get('depends_on', [])) > 0 and
  59. os.path.exists(DROP_IN_MARKER_FILE)):
  60. start_cmd = '/usr/libexec/paunch-start-podman-container %s' % container
  61. else:
  62. start_cmd = '/usr/bin/podman start %s' % container
  63. sysd_unit_f = sysdir + systemctl.format_name(service)
  64. log.debug('Creating systemd unit file: %s' % sysd_unit_f)
  65. s_config = {
  66. 'name': container,
  67. 'start_cmd': start_cmd,
  68. 'wants': wants,
  69. 'restart': restart,
  70. 'stop_grace_period': stop_grace_period,
  71. 'sys_exec': '\n'.join(['%s=%s' % (x, y) for x, y in sys_exec.items()]),
  72. }
  73. # Ensure we don't have some trailing .requires directory and content for
  74. # this service
  75. if os.path.exists(sysd_unit_f + '.requires'):
  76. shutil.rmtree(sysd_unit_f + '.requires')
  77. with open(sysd_unit_f, 'w') as unit_file:
  78. os.chmod(unit_file.name, 0o644)
  79. unit_file.write("""[Unit]
  80. Description=%(name)s container
  81. After=paunch-container-shutdown.service
  82. Wants=%(wants)s
  83. [Service]
  84. Restart=%(restart)s
  85. ExecStart=%(start_cmd)s
  86. ExecReload=/usr/bin/podman kill --signal HUP %(name)s
  87. ExecStop=/usr/bin/podman stop -t %(stop_grace_period)s %(name)s
  88. KillMode=none
  89. Type=forking
  90. PIDFile=/var/run/%(name)s.pid
  91. %(sys_exec)s
  92. [Install]
  93. WantedBy=multi-user.target""" % s_config)
  94. try:
  95. systemctl.daemon_reload()
  96. systemctl.enable(service, now=True)
  97. except systemctl.SystemctlException:
  98. log.exception("systemctl failed")
  99. raise
  100. def service_delete(container, sysdir=constants.SYSTEMD_DIR, log=None):
  101. """Delete a service in systemd
  102. :param container: container name
  103. :type container: String
  104. :param sysdir: systemd unit files directory
  105. :type sysdir: string
  106. :param log: optional pre-defined logger for messages
  107. :type log: logging.RootLogger
  108. """
  109. log = log or common.configure_logging(__name__)
  110. # prefix is explained in the service_create().
  111. service = 'tripleo_' + container
  112. sysd_unit_f = systemctl.format_name(service)
  113. sysd_health_f = systemctl.format_name(service + '_healthcheck')
  114. sysd_timer_f = service + '_healthcheck.timer'
  115. sysd_health_req_d = sysd_unit_f + '.requires'
  116. for sysd_f in sysd_unit_f, sysd_health_f, sysd_timer_f:
  117. if os.path.isfile(sysdir + sysd_f):
  118. log.debug('Stopping and disabling systemd service for %s' %
  119. service)
  120. try:
  121. systemctl.stop(sysd_f)
  122. systemctl.disable(sysd_f)
  123. except systemctl.SystemctlException:
  124. log.exception("systemctl failed")
  125. raise
  126. log.debug('Removing systemd unit file %s' % sysd_f)
  127. os.remove(sysdir + sysd_f)
  128. else:
  129. log.info('No systemd unit file was found for %s' % sysd_f)
  130. # Now that the service is removed, we can remove its ".requires"
  131. if os.path.exists(os.path.join(sysdir, sysd_health_req_d)):
  132. log.info('Removing healthcheck require for %s' % service)
  133. shutil.rmtree(os.path.join(sysdir, sysd_health_req_d))
  134. def healthcheck_create(container, sysdir='/etc/systemd/system/',
  135. log=None, test='/openstack/healthcheck'):
  136. """Create a healthcheck for a service in systemd
  137. :param container: container name
  138. :type container: String
  139. :param sysdir: systemd unit files directory
  140. :type sysdir: String
  141. :param log: optional pre-defined logger for messages
  142. :type log: logging.RootLogger
  143. :param test: optional test full command
  144. :type test: String
  145. """
  146. log = log or common.configure_logging(__name__)
  147. service = 'tripleo_' + container
  148. healthcheck = systemctl.format_name(service + '_healthcheck')
  149. sysd_unit_f = sysdir + healthcheck
  150. log.debug('Creating systemd unit file: %s' % sysd_unit_f)
  151. s_config = {
  152. 'name': container,
  153. 'service': service,
  154. 'restart': 'restart',
  155. 'test': test,
  156. }
  157. with open(sysd_unit_f, 'w') as unit_file:
  158. os.chmod(unit_file.name, 0o644)
  159. unit_file.write("""[Unit]
  160. Description=%(name)s healthcheck
  161. After=paunch-container-shutdown.service %(service)s.service
  162. Requisite=%(service)s.service
  163. [Service]
  164. Type=oneshot
  165. ExecStart=/usr/bin/podman exec %(name)s %(test)s
  166. SyslogIdentifier=healthcheck_%(name)s
  167. [Install]
  168. WantedBy=multi-user.target
  169. """ % s_config)
  170. def healthcheck_timer_create(container, cconfig, sysdir='/etc/systemd/system/',
  171. log=None):
  172. """Create a systemd timer for a healthcheck
  173. :param container: container name
  174. :type container: String
  175. :param cconfig: container configuration
  176. :type cconfig: Dictionary
  177. :param sysdir: systemd unit files directory
  178. :type sysdir: string
  179. :param log: optional pre-defined logger for messages
  180. :type log: logging.RootLogger
  181. """
  182. log = log or common.configure_logging(__name__)
  183. service = 'tripleo_' + container
  184. healthcheck_timer = service + '_healthcheck.timer'
  185. sysd_timer_f = sysdir + healthcheck_timer
  186. log.debug('Creating systemd timer file: %s' % sysd_timer_f)
  187. interval = cconfig.get('check_interval', 60)
  188. s_config = {
  189. 'name': container,
  190. 'service': service,
  191. 'interval': interval,
  192. 'randomize': int(interval) * 3 / 4
  193. }
  194. with open(sysd_timer_f, 'w') as timer_file:
  195. os.chmod(timer_file.name, 0o644)
  196. timer_file.write("""[Unit]
  197. Description=%(name)s container healthcheck
  198. PartOf=%(service)s.service
  199. [Timer]
  200. OnActiveSec=120
  201. OnUnitActiveSec=%(interval)s
  202. RandomizedDelaySec=%(randomize)s
  203. [Install]
  204. WantedBy=timers.target""" % s_config)
  205. try:
  206. systemctl.enable(healthcheck_timer, now=True)
  207. systemctl.add_requires(systemctl.format_name(service),
  208. healthcheck_timer)
  209. systemctl.daemon_reload()
  210. except systemctl.SystemctlException:
  211. log.exception("systemctl failed")
  212. raise