Merge "Stop relying on future mtime in tripleo_nova_image_cache"
This commit is contained in:
commit
c49f8d26b8
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
#
|
#
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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 }}"
|
|
||||||
|
|
|
@ -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
|
|
Loading…
Reference in New Issue