359ef43f94
The tripleo_nova_image_cache role was relying on a future mtime to convince nova image cleanup logic to skip a specific image that we want to pre-cache. This works well on nodes that are not currently hosting an instance of the image, however if there is an instance using it then nova will reset the mtime every 40 minutes (by default). As a result it's not possible to provide fine grained control of the caching. We can just ensure the image exists on the host with an mtime of now. It should remain cache for at least remove_unused_original_minimum_age_seconds from nova.conf (default 86400). Change-Id: If836759020bcaeb0d3af47f2af7f98ff2150b606
222 lines
7.9 KiB
Python
222 lines
7.9 KiB
Python
#!/usr/bin/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.
|
|
__metaclass__ = type
|
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
from ansible.module_utils.openstack import openstack_cloud_from_module
|
|
from ansible.module_utils.openstack import openstack_full_argument_spec
|
|
from ansible.module_utils.openstack import openstack_module_kwargs
|
|
import datetime
|
|
import hashlib
|
|
import os
|
|
import tempfile
|
|
import time
|
|
|
|
ANSIBLE_METADATA = {
|
|
'metadata_version': '1.1',
|
|
'status': ['preview'],
|
|
'supported_by': 'community'
|
|
}
|
|
|
|
|
|
DOCUMENTATION = '''
|
|
---
|
|
module: tripleo_nova_image_cache
|
|
short_description: Manage Nova image cache on TripleO OpenStack deployment
|
|
version_added: "2.0"
|
|
author: "Oliver Walsh (@owalsh)"
|
|
description:
|
|
- Manage Nova image cache on TripleO OpenStack deployment
|
|
options:
|
|
id:
|
|
description:
|
|
- ID of the image to cache
|
|
required: true
|
|
scp_source:
|
|
description:
|
|
- Attempt to scp the image from this nova-compute host
|
|
scp_continue_on_error:
|
|
description:
|
|
- Fallback to image download if scp fails
|
|
default: false
|
|
|
|
requirements: ["openstacksdk", "tripleo-common"]
|
|
'''
|
|
|
|
EXAMPLES = '''
|
|
- name: Cache image
|
|
tripleo_nova_image_cache:
|
|
id: ec151bd1-aab4-413c-b577-ced089e7d3f8
|
|
|
|
- name: Cache image, try to copy from existing host
|
|
tripleo_nova_image_cache:
|
|
id: ec151bd1-aab4-413c-b577-ced089e7d3f8
|
|
scp_source: nova-compute-0
|
|
scp_continue_on_error: true
|
|
|
|
'''
|
|
|
|
|
|
def main():
|
|
|
|
argument_spec = openstack_full_argument_spec(
|
|
id=dict(required=True),
|
|
_cache_dir=dict(required=True),
|
|
_cache_file=dict(required=True),
|
|
_chunk_size=dict(default=64 * 1024, type='int'),
|
|
_prefetched_path=dict(default=None),
|
|
scp_continue_on_error=dict(default=False, type='bool')
|
|
)
|
|
module_kwargs = openstack_module_kwargs()
|
|
module = AnsibleModule(argument_spec, **module_kwargs)
|
|
|
|
image_id = module.params['id']
|
|
cache_dir = module.params['_cache_dir']
|
|
cache_file = module.params['_cache_file']
|
|
chunk_size = module.params['_chunk_size']
|
|
prefetched_path = module.params['_prefetched_path']
|
|
scp_continue = module.params['scp_continue_on_error']
|
|
|
|
result = dict(
|
|
changed=False,
|
|
actions=[],
|
|
image=None,
|
|
cache_file='',
|
|
exists_in_cache=False,
|
|
mtime=0
|
|
)
|
|
|
|
sdk, cloud = openstack_cloud_from_module(module, min_version='0.11.3')
|
|
|
|
try:
|
|
result['exists_in_cache'] = exists_in_cache = os.path.exists(
|
|
cache_file)
|
|
if exists_in_cache:
|
|
result['cache_file'] = cache_file
|
|
|
|
image = cloud.image.find_image(name_or_id=image_id)
|
|
exists_in_glance = image is not None
|
|
if exists_in_glance:
|
|
result['image'] = image.to_dict()
|
|
|
|
if not exists_in_cache:
|
|
|
|
if not exists_in_glance:
|
|
module.fail_json(
|
|
msg="Image not found in glance: %s" % image_id)
|
|
|
|
md5 = hashlib.md5()
|
|
if prefetched_path:
|
|
result['actions'].append({
|
|
'name': 'Verify pre-fetched image checksum'
|
|
})
|
|
with open(prefetched_path, 'rb') as prefetched_image_file:
|
|
while True:
|
|
chunk = prefetched_image_file.read(chunk_size)
|
|
if not chunk:
|
|
break
|
|
md5.update(chunk)
|
|
prefetched_checksum = md5.hexdigest()
|
|
if prefetched_checksum == image.checksum:
|
|
result['actions'].append({
|
|
'name': 'Verify pre-fetched image',
|
|
'result': True,
|
|
'expected_md5': image.checksum,
|
|
'actual_md5': prefetched_checksum
|
|
})
|
|
# FIXME: chown to the container nova uid (42436)
|
|
# until we can run within the container
|
|
os.chown(prefetched_path, 42436, 42436)
|
|
os.rename(prefetched_path, cache_file)
|
|
result['changed'] = True
|
|
else:
|
|
result['actions'].append({
|
|
'name': 'Verify pre-fetched image',
|
|
'result': False,
|
|
'expected_md5': image.checksum,
|
|
'actual_md5': prefetched_checksum
|
|
})
|
|
if not scp_continue:
|
|
module.fail_json(
|
|
msg="Pre-fetched image checksum failed")
|
|
# Ignore it and download direct from glance.
|
|
# As we did not create it we should not remove it.
|
|
prefetched_path = ''
|
|
|
|
if not prefetched_path:
|
|
with tempfile.NamedTemporaryFile(
|
|
'wb',
|
|
dir=cache_dir,
|
|
delete=False) as temp_cache_file:
|
|
try:
|
|
md5 = hashlib.md5()
|
|
image_stream = cloud.image.download_image(
|
|
image,
|
|
stream=True
|
|
)
|
|
try:
|
|
for chunk in image_stream.iter_content(
|
|
chunk_size=chunk_size):
|
|
md5.update(chunk)
|
|
temp_cache_file.write(chunk)
|
|
finally:
|
|
image_stream.close()
|
|
temp_cache_file.close()
|
|
|
|
download_checksum = md5.hexdigest()
|
|
if download_checksum != image.checksum:
|
|
result['actions'].append({
|
|
'name': 'Verify downloaded image',
|
|
'result': False,
|
|
'expected_md5': image.checksum,
|
|
'actual_md5': download_checksum
|
|
})
|
|
module.fail_json(
|
|
msg="Image data does not match checksum")
|
|
result['actions'].append({
|
|
'name': 'Verify downloaded image',
|
|
'result': True,
|
|
'expected_md5': image.checksum,
|
|
'actual_md5': download_checksum
|
|
})
|
|
|
|
# FIXME: chown to the container nova uid (42436)
|
|
# until we can run within the container
|
|
os.chown(temp_cache_file.name, 42436, 42436)
|
|
os.rename(temp_cache_file.name, cache_file)
|
|
result['changed'] = True
|
|
finally:
|
|
try:
|
|
os.unlink(temp_cache_file.name)
|
|
except Exception:
|
|
pass
|
|
|
|
# Always set the mtime to now but don't report this as a change
|
|
# as this is constantly refreshed by nova (every 40mins by default)
|
|
# while an instance on the host is using the image
|
|
now = time.time()
|
|
os.utime(cache_file, (now, now))
|
|
result['mtime'] = now
|
|
|
|
module.exit_json(**result)
|
|
|
|
except sdk.exceptions.OpenStackCloudException as e:
|
|
module.fail_json(msg=str(e), extra_data=e.extra_data)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|