Refactor virtualbmc-domain tasks into a module
Virtualbmc domain creation can be unreliable, in particular when domains already exist. This is in part due to the vbmc stop command not functioning properly [1]. By moving the domain management into a Python module we can better control the process of creation, and improve performance. [1] https://storyboard.openstack.org/#!/story/2003534 Change-Id: I52cb08cd0d300630cb6341f50eb0484c1d16daa4
This commit is contained in:
parent
d8f380a975
commit
75273302ed
163
ansible/roles/virtualbmc-domain/library/virtualbmc_domain.py
Normal file
163
ansible/roles/virtualbmc-domain/library/virtualbmc_domain.py
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# 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 ansible.module_utils.basic import AnsibleModule # noqa
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os.path
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
---
|
||||||
|
module: virtualbmc_domain
|
||||||
|
short_description: Manages domains in VirtualBMC.
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
RETRIES = 60
|
||||||
|
INTERVAL = 0.5
|
||||||
|
|
||||||
|
|
||||||
|
def _vbmc_command(module, args):
|
||||||
|
path_prefix = ("%s/bin" % module.params["virtualenv"]
|
||||||
|
if module.params["virtualenv"] else None)
|
||||||
|
cmd = ["vbmc", "--no-daemon"]
|
||||||
|
if module.params["log_directory"]:
|
||||||
|
log_file = os.path.join(module.params["log_directory"],
|
||||||
|
"vbmc-%s.log" % module.params["domain"])
|
||||||
|
cmd += ["--log-file", log_file]
|
||||||
|
cmd += args
|
||||||
|
result = module.run_command(cmd, check_rc=True, path_prefix=path_prefix)
|
||||||
|
rc, out, err = result
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def _get_domain(module, allow_missing=False):
|
||||||
|
domain_name = module.params["domain"]
|
||||||
|
if allow_missing:
|
||||||
|
# Use a list to avoid failing if the domain does not exist.
|
||||||
|
domains = _vbmc_command(module, ["list", "-f", "json"])
|
||||||
|
domains = json.loads(domains)
|
||||||
|
# vbmc list returns a list of dicts. Transform into a dict of dicts
|
||||||
|
# keyed by domain name.
|
||||||
|
domains = {d["Domain name"]: d for d in domains}
|
||||||
|
try:
|
||||||
|
return domains[domain_name]
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
domain = _vbmc_command(module, ["show", domain_name, "-f", "json"])
|
||||||
|
domain = json.loads(domain)
|
||||||
|
domain = {field["Property"]: field["Value"] for field in domain}
|
||||||
|
return domain
|
||||||
|
|
||||||
|
|
||||||
|
def _add_domain(module):
|
||||||
|
domain_name = module.params["domain"]
|
||||||
|
args = [
|
||||||
|
"add", domain_name,
|
||||||
|
"--address", module.params["ipmi_address"],
|
||||||
|
"--port", str(module.params["ipmi_port"]),
|
||||||
|
"--username", module.params["ipmi_username"],
|
||||||
|
"--password", module.params["ipmi_password"],
|
||||||
|
]
|
||||||
|
if module.params["libvirt_uri"]:
|
||||||
|
args += ["--libvirt-uri", module.params["libvirt_uri"]]
|
||||||
|
_vbmc_command(module, args)
|
||||||
|
|
||||||
|
|
||||||
|
def _wait_for_existence(module, exists):
|
||||||
|
"""Wait for the domain to exist or cease existing."""
|
||||||
|
for _ in range(RETRIES):
|
||||||
|
domain = _get_domain(module, allow_missing=True)
|
||||||
|
if (exists and domain) or (not exists and not domain):
|
||||||
|
return
|
||||||
|
time.sleep(INTERVAL)
|
||||||
|
action = "added" if exists else "deleted"
|
||||||
|
module.fail_json(msg="Timed out waiting for domain %s to be %s" %
|
||||||
|
(module.params['domain'], action))
|
||||||
|
|
||||||
|
|
||||||
|
def _wait_for_status(module, status):
|
||||||
|
"""Wait for the domain to reach a particular status."""
|
||||||
|
for _ in range(RETRIES):
|
||||||
|
domain = _get_domain(module)
|
||||||
|
if domain["status"] == status:
|
||||||
|
return
|
||||||
|
time.sleep(INTERVAL)
|
||||||
|
module.fail_json(msg="Timed out waiting for domain %s to reach status "
|
||||||
|
"%s" % (module.params['domain'], status))
|
||||||
|
|
||||||
|
|
||||||
|
def _virtualbmc_domain(module):
|
||||||
|
"""Configure a VirtualBMC domain."""
|
||||||
|
changed = False
|
||||||
|
domain_name = module.params["domain"]
|
||||||
|
|
||||||
|
# Even if the domain is present in VBMC, we can't guarantee that it's
|
||||||
|
# configured correctly. It's easiest to delete and re-add it; this should
|
||||||
|
# involve minimal downtime.
|
||||||
|
domain = _get_domain(module, allow_missing=True)
|
||||||
|
if domain and domain["Status"] == "running":
|
||||||
|
if not module.check_mode:
|
||||||
|
module.debug("Stopping domain %s" % domain_name)
|
||||||
|
_vbmc_command(module, ["stop", domain_name])
|
||||||
|
_wait_for_status(module, "down")
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
if domain:
|
||||||
|
if not module.check_mode:
|
||||||
|
module.debug("Deleting domain %s" % domain_name)
|
||||||
|
_vbmc_command(module, ["delete", domain_name])
|
||||||
|
_wait_for_existence(module, False)
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
if module.params['state'] == 'present':
|
||||||
|
if not module.check_mode:
|
||||||
|
module.debug("Adding domain %s" % domain_name)
|
||||||
|
_add_domain(module)
|
||||||
|
_wait_for_existence(module, True)
|
||||||
|
|
||||||
|
module.debug("Starting domain %s" % domain_name)
|
||||||
|
_vbmc_command(module, ["start", domain_name])
|
||||||
|
_wait_for_status(module, "running")
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
return {"changed": changed}
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
module = AnsibleModule(
|
||||||
|
argument_spec=dict(
|
||||||
|
domain=dict(type='str', required=True),
|
||||||
|
ipmi_address=dict(type='str', required=True),
|
||||||
|
ipmi_port=dict(type='int', required=True),
|
||||||
|
ipmi_username=dict(type='str', required=True),
|
||||||
|
ipmi_password=dict(type='str', required=True, no_log=True),
|
||||||
|
libvirt_uri=dict(type='str'),
|
||||||
|
log_directory=dict(type='str'),
|
||||||
|
state=dict(type=str, default='present',
|
||||||
|
choices=['present', 'absent']),
|
||||||
|
virtualenv=dict(type='str'),
|
||||||
|
),
|
||||||
|
supports_check_mode=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
result = _virtualbmc_domain(module)
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
@ -1,69 +1,13 @@
|
|||||||
---
|
---
|
||||||
- name: Set VBMC command string
|
- name: Ensure domain {{ vbmc_domain }} is configured
|
||||||
vars:
|
virtualbmc_domain:
|
||||||
vbmc_path: >-
|
domain: "{{ vbmc_domain }}"
|
||||||
{{ vbmc_virtualenv_path ~ '/bin/vbmc'
|
ipmi_address: "{{ vbmc_ipmi_address }}"
|
||||||
if vbmc_virtualenv_path
|
ipmi_port: "{{ vbmc_ipmi_port }}"
|
||||||
else '/usr/local/bin/vbmc' }}
|
ipmi_username: "{{ vbmc_ipmi_username }}"
|
||||||
set_fact:
|
ipmi_password: "{{ vbmc_ipmi_password }}"
|
||||||
# vbmcd should already be running, so --no-daemon stops vbmc from spawning
|
libvirt_uri: "{{ vbmc_libvirt_uri | default(omit, true) }}"
|
||||||
# another instance of the daemon.
|
log_directory: "{{ vbmc_log_directory | default(omit, true) }}"
|
||||||
vbmc_cmd: >-
|
state: "{{ vbmc_state }}"
|
||||||
'{{ vbmc_path }}'
|
virtualenv: "{{ vbmc_virtualenv_path | default(omit, true) }}"
|
||||||
--no-daemon
|
|
||||||
{% if vbmc_log_directory is not none %}
|
|
||||||
--log-file '{{ vbmc_log_directory }}/vbmc-{{ vbmc_domain }}.log'
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
# Even if the domain is present in VBMC, we can't guarantee that it's
|
|
||||||
# configured correctly. It's easiest to delete and re-add it; this should
|
|
||||||
# involve minimal downtime.
|
|
||||||
- name: Ensure domain is stopped and deleted in VBMC
|
|
||||||
command: >-
|
|
||||||
{{ vbmc_cmd }} {{ item }} '{{ vbmc_domain }}'
|
|
||||||
loop:
|
|
||||||
- stop
|
|
||||||
- delete
|
|
||||||
register: res
|
|
||||||
changed_when: res.rc == 0
|
|
||||||
failed_when:
|
|
||||||
- res.rc != 0
|
|
||||||
- "'No domain with matching name' not in res.stderr"
|
|
||||||
become: true
|
become: true
|
||||||
|
|
||||||
# The commands above tend to return before the daemon has completed the action.
|
|
||||||
# Check here to be safe.
|
|
||||||
- name: Wait to ensure socket is closed
|
|
||||||
wait_for:
|
|
||||||
host: "{{ vbmc_ipmi_address }}"
|
|
||||||
port: "{{ vbmc_ipmi_port }}"
|
|
||||||
state: stopped
|
|
||||||
timeout: 15
|
|
||||||
|
|
||||||
# These tasks will trigger ansible lint rule ANSIBLE0012 because they are not
|
|
||||||
# idempotent (we always delete and recreate the domain). Use a tag to suppress
|
|
||||||
# the checks.
|
|
||||||
- name: Ensure domain is added to VBMC
|
|
||||||
command: >-
|
|
||||||
{{ vbmc_cmd }} add '{{ vbmc_domain }}'
|
|
||||||
--port {{ vbmc_ipmi_port }}
|
|
||||||
--username '{{ vbmc_ipmi_username }}'
|
|
||||||
--password '{{ vbmc_ipmi_password }}'
|
|
||||||
--address {{ vbmc_ipmi_address }}
|
|
||||||
{% if vbmc_libvirt_uri %} --libvirt-uri '{{ vbmc_libvirt_uri }}'{% endif %}
|
|
||||||
when: vbmc_state == 'present'
|
|
||||||
become: true
|
|
||||||
tags:
|
|
||||||
- skip_ansible_lint
|
|
||||||
|
|
||||||
- name: Ensure domain is started in VBMC
|
|
||||||
command: >
|
|
||||||
{{ vbmc_cmd }} start '{{ vbmc_domain }}'
|
|
||||||
register: res
|
|
||||||
# Retry a few times in case the VBMC daemon has been slow to process the last
|
|
||||||
# few commands.
|
|
||||||
until: res is succeeded
|
|
||||||
when: vbmc_state == 'present'
|
|
||||||
become: true
|
|
||||||
tags:
|
|
||||||
- skip_ansible_lint
|
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Improves reliability and performance of VirtualBMC domain management.
|
Loading…
Reference in New Issue
Block a user