Merge "Stop relying on future mtime in tripleo_nova_image_cache"

This commit is contained in:
Zuul 2020-09-02 17:48:16 +00:00 committed by Gerrit Code Review
commit c49f8d26b8
5 changed files with 86 additions and 182 deletions

View File

@ -45,16 +45,6 @@ options:
description: description:
- ID of the image to cache - ID of the image to cache
required: true required: true
ttl:
description:
- Ensure the image remains cached for at least ttl days
default: 7
state:
description:
- Whether the image be present in cache or expired
(nova should delete it later if it is no longer used)
choices: [present, expired]
default: present
scp_source: scp_source:
description: description:
- Attempt to scp the image from this nova-compute host - Attempt to scp the image from this nova-compute host
@ -67,41 +57,23 @@ requirements: ["openstacksdk", "tripleo-common"]
''' '''
EXAMPLES = ''' EXAMPLES = '''
- name: Cache image for at least 2 weeks - name: Cache image
tripleo_nova_image_cache: tripleo_nova_image_cache:
id: ec151bd1-aab4-413c-b577-ced089e7d3f8 id: ec151bd1-aab4-413c-b577-ced089e7d3f8
ttl: 14
- name: Allow nova to cleanup this image from cache
tripleo_nova_image_cache:
id: ec151bd1-aab4-413c-b577-ced089e7d3f8
state: expired
- name: Cache image, try to copy from existing host - name: Cache image, try to copy from existing host
tripleo_nova_image_cache: tripleo_nova_image_cache:
id: ec151bd1-aab4-413c-b577-ced089e7d3f8 id: ec151bd1-aab4-413c-b577-ced089e7d3f8
ttl: 14
scp_source: nova-compute-0 scp_source: nova-compute-0
scp_continue_on_error: true scp_continue_on_error: true
''' '''
def ttl_to_ts(ttl_days):
"""Return a timestamp at midnight UTC ttl_days in the future"""
epoch = datetime.datetime.utcfromtimestamp(0)
midnight_today = datetime.datetime.utcnow().replace(
hour=0, minute=0, second=0, microsecond=0)
expiry = midnight_today + datetime.timedelta(days=ttl_days+1)
return (expiry - epoch).total_seconds()
def main(): def main():
argument_spec = openstack_full_argument_spec( argument_spec = openstack_full_argument_spec(
id=dict(required=True), id=dict(required=True),
ttl=dict(default=7, type='int', min=1),
state=dict(default='present', choices=['expired', 'present']),
_cache_dir=dict(required=True), _cache_dir=dict(required=True),
_cache_file=dict(required=True), _cache_file=dict(required=True),
_chunk_size=dict(default=64 * 1024, type='int'), _chunk_size=dict(default=64 * 1024, type='int'),
@ -118,8 +90,6 @@ def main():
prefetched_path = module.params['_prefetched_path'] prefetched_path = module.params['_prefetched_path']
scp_continue = module.params['scp_continue_on_error'] scp_continue = module.params['scp_continue_on_error']
ttl_days = module.params['ttl']
result = dict( result = dict(
changed=False, changed=False,
actions=[], actions=[],
@ -142,134 +112,104 @@ def main():
if exists_in_glance: if exists_in_glance:
result['image'] = image.to_dict() result['image'] = image.to_dict()
if module.params['state'] == 'present': if not exists_in_cache:
if not exists_in_cache:
if not exists_in_glance: if not exists_in_glance:
module.fail_json( module.fail_json(
msg="Image not found in glance: %s" % image_id) msg="Image not found in glance: %s" % image_id)
md5 = hashlib.md5() md5 = hashlib.md5()
if prefetched_path: 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({ result['actions'].append({
'name': 'Verify pre-fetched image checksum' 'name': 'Verify pre-fetched image',
'result': True,
'expected_md5': image.checksum,
'actual_md5': prefetched_checksum
}) })
with open(prefetched_path, 'rb') as prefetched_image_file: # FIXME: chown to the container nova uid (42436)
while True: # until we can run within the container
chunk = prefetched_image_file.read(chunk_size) os.chown(prefetched_path, 42436, 42436)
if not chunk: os.rename(prefetched_path, cache_file)
break result['changed'] = True
md5.update(chunk) else:
prefetched_checksum = md5.hexdigest() result['actions'].append({
if prefetched_checksum == image.checksum: 'name': 'Verify pre-fetched image',
result['actions'].append({ 'result': False,
'name': 'Verify pre-fetched image', 'expected_md5': image.checksum,
'result': True, 'actual_md5': prefetched_checksum
'expected_md5': image.checksum, })
'actual_md5': prefetched_checksum if not scp_continue:
}) module.fail_json(
# FIXME: chown to the container nova uid (42436) msg="Pre-fetched image checksum failed")
# until we can run within the container # Ignore it and download direct from glance.
os.chown(prefetched_path, 42436, 42436) # As we did not create it we should not remove it.
os.rename(prefetched_path, cache_file) prefetched_path = ''
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: if not prefetched_path:
with tempfile.NamedTemporaryFile( with tempfile.NamedTemporaryFile(
'wb', 'wb',
dir=cache_dir, dir=cache_dir,
delete=False) as temp_cache_file: delete=False) as temp_cache_file:
try:
md5 = hashlib.md5()
image_stream = cloud.image.download_image(
image,
stream=True
)
try: try:
md5 = hashlib.md5() for chunk in image_stream.iter_content(
image_stream = cloud.image.download_image( chunk_size=chunk_size):
image, md5.update(chunk)
stream=True temp_cache_file.write(chunk)
) finally:
try: image_stream.close()
for chunk in image_stream.iter_content( temp_cache_file.close()
chunk_size=chunk_size):
md5.update(chunk)
temp_cache_file.write(chunk)
finally:
image_stream.close()
temp_cache_file.close()
download_checksum = md5.hexdigest() download_checksum = md5.hexdigest()
if download_checksum != image.checksum: 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({ result['actions'].append({
'name': 'Verify downloaded image', 'name': 'Verify downloaded image',
'result': True, 'result': False,
'expected_md5': image.checksum, 'expected_md5': image.checksum,
'actual_md5': download_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) # FIXME: chown to the container nova uid (42436)
# until we can run within the container # until we can run within the container
os.chown(temp_cache_file.name, 42436, 42436) os.chown(temp_cache_file.name, 42436, 42436)
os.rename(temp_cache_file.name, cache_file) os.rename(temp_cache_file.name, cache_file)
result['changed'] = True result['changed'] = True
finally: finally:
try: try:
os.unlink(temp_cache_file.name) os.unlink(temp_cache_file.name)
except Exception: except Exception:
pass pass
# Set the mtime in the future to prevent nova cleanup # Always set the mtime to now but don't report this as a change
cache_file_stat = os.stat(cache_file) # as this is constantly refreshed by nova (every 40mins by default)
expiry_ts = ttl_to_ts(ttl_days) # while an instance on the host is using the image
now = time.time() now = time.time()
if cache_file_stat.st_mtime != expiry_ts: os.utime(cache_file, (now, now))
os.utime(cache_file, (now, expiry_ts)) result['mtime'] = now
result['actions'].append({
'name': 'Update mtime',
'from': cache_file_stat.st_mtime,
'to': expiry_ts
})
result['changed'] = True
else: # expired
if not exists_in_cache:
result['changed'] = False
else:
# Set the mtime to epoch to enable nova cleanup
now = time.time()
ts = 0
cache_file_stat = os.stat(cache_file)
if cache_file_stat.st_mtime > ts:
os.utime(cache_file, (now, ts))
result['actions'].append({
'name': 'Update mtime',
'from': cache_file_stat.st_mtime,
'to': ts
})
result['changed'] = True
cache_file_stat = os.stat(cache_file)
result['mtime'] = cache_file_stat.st_mtime
result['expires'] = time.strftime(
"%a, %d %b %Y %H:%M:%S %z",
time.localtime(cache_file_stat.st_mtime)
)
module.exit_json(**result) module.exit_json(**result)

View File

@ -42,9 +42,7 @@
# test_args1.yml # test_args1.yml
# tripleo_nova_image_cache_images: # tripleo_nova_image_cache_images:
# - id: d23c6b8f-e166-4a02-afd8-0ae8d6f73f18 # - id: d23c6b8f-e166-4a02-afd8-0ae8d6f73f18
# state: expired
# - id: 81bbb16-d589-4730-be70-822a82ab6bb9 # - id: 81bbb16-d589-4730-be70-822a82ab6bb9
# ttl: 28
# #
# Multi-stack inventory: # Multi-stack inventory:
# #
@ -53,9 +51,7 @@
# tripleo_nova_image_cache_plan: edge0 # tripleo_nova_image_cache_plan: edge0
# tripleo_nova_image_cache_images: # tripleo_nova_image_cache_images:
# - id: d23c6b8f-e166-4a02-afd8-0ae8d6f73f18 # - id: d23c6b8f-e166-4a02-afd8-0ae8d6f73f18
# state: expired
# - id: 81bbb16-d589-4730-be70-822a82ab6bb9 # - id: 81bbb16-d589-4730-be70-822a82ab6bb9
# ttl: 28
# tripleo_nova_image_cache_use_proxy: true # tripleo_nova_image_cache_use_proxy: true
# tripleo_nova_image_cache_proxy_hostname: compute-1 # optional, first nova_compute host is used otherwise # tripleo_nova_image_cache_proxy_hostname: compute-1 # optional, first nova_compute host is used otherwise
# #

View File

@ -25,8 +25,6 @@
become: true become: true
tripleo_nova_image_cache: tripleo_nova_image_cache:
id: "{{ image.id }}" id: "{{ image.id }}"
ttl: "{{ image.ttl|default(omit) }}"
state: "{{ image.state|default(omit) }}"
any_errors_fatal: "{{ true if tripleo_nova_image_cache_use_proxy and tripleo_nova_image_cache_is_proxy_host else false }}" any_errors_fatal: "{{ true if tripleo_nova_image_cache_use_proxy and tripleo_nova_image_cache_is_proxy_host else false }}"
when: when:
- not (tripleo_nova_image_cache_use_proxy | bool) or (tripleo_nova_image_cache_is_proxy_host | bool) - not (tripleo_nova_image_cache_use_proxy | bool) or (tripleo_nova_image_cache_is_proxy_host | bool)
@ -35,8 +33,6 @@
become: true become: true
tripleo_nova_image_cache: tripleo_nova_image_cache:
id: "{{ image.id }}" id: "{{ image.id }}"
ttl: "{{ image.ttl|default(omit) }}"
state: "{{ image.state|default(omit) }}"
scp_source: "{{ tripleo_nova_image_cache_proxy_source_ip }}" scp_source: "{{ tripleo_nova_image_cache_proxy_source_ip }}"
scp_continue_on_error: "{{ tripleo_nova_image_cache_ignore_proxy_error }}" scp_continue_on_error: "{{ tripleo_nova_image_cache_ignore_proxy_error }}"
when: when:

View File

@ -46,12 +46,5 @@
image: "{{ item }}" image: "{{ item }}"
loop: "{{ [ loop: "{{ [
tripleo_nova_image_cache_images|selectattr('state', 'undefined')|list, tripleo_nova_image_cache_images|selectattr('state', 'undefined')|list,
tripleo_nova_image_cache_images|selectattr('state', 'defined')|rejectattr('state', 'equalto', 'expired')|list tripleo_nova_image_cache_images|selectattr('state', 'defined')|selectattr('state', 'equalto', 'present')|list
]|flatten }}" ]|flatten }}"
- name: Uncache images
include_tasks: uncache.yml
vars:
image: "{{ item }}"
loop: "{{ tripleo_nova_image_cache_images|rejectattr('state', 'undefined')|selectattr('state', 'equalto', 'expired')|list }}"

View File

@ -1,21 +0,0 @@
---
# 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: "Uncache image {{ image.id }}"
become: true
tripleo_nova_image_cache:
id: "{{ image.id }}"
state: expired