Skip to content
This repository has been archived by the owner on Apr 7, 2022. It is now read-only.

[1LP][RFR] Automate multiple vm retirement, fix ec2 instance-backed retirement #9540

Merged
merged 1 commit into from
Jan 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cfme/cloud/instance/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from widgetastic_manageiq import Accordion
from widgetastic_manageiq import CompareToolBarActionsView
from widgetastic_manageiq import ManageIQTree
from widgetastic_manageiq import PaginationPane
from widgetastic_manageiq import Search
from widgetastic_manageiq import SummaryTable

Expand Down Expand Up @@ -114,6 +115,7 @@ def is_displayed(self):
sidebar = View.nested(InstanceAccordion)
search = View.nested(Search)
including_entities = View.include(VMEntities, use_parent=True)
paginator = PaginationPane()


class InstanceProviderAllView(CloudInstanceView):
Expand Down
171 changes: 116 additions & 55 deletions cfme/common/vm.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import json
from datetime import date
from datetime import datetime
from datetime import timedelta

import attr
from cached_property import cached_property
Expand All @@ -18,6 +17,7 @@
from cfme.common.vm_console import ConsoleMixin
from cfme.common.vm_views import DriftAnalysis
from cfme.common.vm_views import DriftHistory
from cfme.common.vm_views import RetirementViewWithOffset
from cfme.common.vm_views import RightSizeView
from cfme.common.vm_views import VMPropertyDetailView
from cfme.exceptions import CFMEException
Expand Down Expand Up @@ -713,9 +713,14 @@ def exists_on_provider(self):
return self.provider.mgmt.does_vm_exist(self.name)

def retire(self):
"""Navigate to VM's | Instance's Details page and retire it by selecting
Lifecycle > Retire this VM | Instance
"""
view = navigate_to(self, 'Details', use_resetter=False)
view.toolbar.reload.click()
view.toolbar.lifecycle.item_select(self.TO_RETIRE, handle_alert=True)

view = self.create_view(RequestsView)
view.flash.assert_no_error()

def power_control_from_cfme(self, option, cancel=True, from_details=False):
Expand Down Expand Up @@ -869,8 +874,6 @@ def set_retirement_date(self, when=None, offset=None, warn=None):
offset: :py:class:`dict` with months, weeks, days, hours keys. other keys ignored
warn: When to warn, fills the select in the form in case the ``when`` is specified.

Note: this should be moved up to the common VM class when infra+cloud+common are all WT

If when and offset are both None, this removes retirement date

Examples:
Expand All @@ -889,61 +892,35 @@ def set_retirement_date(self, when=None, offset=None, warn=None):
An enhancement to cfme.utils.timeutil extending timedelta would be great for making this a
bit cleaner
"""
view = navigate_to(self, 'SetRetirement')
changed = False
fill_date = None
fill_offset = None

# explicit is/not None use here because of empty strings and dicts

if when is not None and offset is not None:
raise ValueError('set_retirement_date takes when or offset, but not both')
if when is not None and not isinstance(when, (datetime, date)):
raise ValueError('when argument must be a datetime object')

# due to major differences between the forms and their interaction, I'm splitting this
# method into two major blocks, one for each version. As a result some patterns will be
# repeated in both blocks
# This will allow for making changes to one version or the other without strange
# interaction in the logic

# format the date
# needs 4 digit year for fill
# displayed 2 digit year for flash message
# 59z/G-release retirement
changed = False # just in case it isn't set in logic
if when is not None and offset is None:
# Specific datetime retire, H+M are 00:00 by default if just date passed
fill_date = when.strftime('%m/%d/%Y %H:%M') # 4 digit year
msg_date = when.strftime('%m/%d/%y %H:%M UTC') # two digit year and timestamp
msg = 'Retirement date set to {}'.format(msg_date)
elif when is None and offset is None:
# clearing retirement date with space in textinput,
# using space here as with empty string calendar input is not cleared correctly
fill_date = ' '
msg = 'Retirement date removed'
elif offset is not None:
# retirement by offset
fill_date = None
if when and offset:
raise ValueError("set_retirement_date takes 'when' or 'offset', but not both")
if when and not isinstance(when, (datetime, date)):
raise ValueError("'when' argument must be a datetime object")

if offset:
fill_offset = {k: v
for k, v in offset.items()
if k in ['months', 'weeks', 'days', 'hours']}
# hack together an offset
# timedelta can take weeks, but not months
# copy and pop, only used to generate message, not used for form fill
offset_copy = fill_offset.copy()
if 'months' in offset_copy:
new_weeks = offset_copy.get('weeks', 0) + int(offset_copy.pop('months', 0)) * 4
offset_copy.update({'weeks': new_weeks})

msg_date = datetime.utcnow() + timedelta(**offset_copy)
msg = 'Retirement date set to {}'.format(msg_date.strftime('%m/%d/%y %H:%M UTC'))
# TODO move into before_fill when no need to click away from datetime picker
elif when:
# format using 4-digit year for form fill
fill_date = when.strftime('%m/%d/%Y %H:%M')
else:
# Clear retirement date with a space in TextInput widget.
# Using space here because empty string does not clear date correctly
fill_date = ' '

# navigate to VM's Details page and set retirement date
view = navigate_to(self, 'SetRetirement')
view.form.fill({
'retirement_mode':
'Time Delay from Now' if fill_offset else 'Specific Date and Time'})
'retirement_mode': 'Time Delay from Now' if fill_offset else 'Specific Date and Time'})
view.flush_widget_cache() # since retirement_date is conditional widget
if fill_date is not None: # specific check because of empty string
# two part fill, widget seems to block warn selection when open

if fill_date:
# two-part fill. widget seems to block warn selection when open.
changed_date = view.form.fill({
'retirement_date': {'datetime_select': fill_date}})
view.title.click() # close datetime widget
Expand All @@ -953,16 +930,16 @@ def set_retirement_date(self, when=None, offset=None, warn=None):
changed = view.form.fill({
'retirement_date': fill_offset, 'retirement_warning': warn})

# Form save and flash messages are the same between versions
if changed:
view.form.save.click()
else:
logger.info('No form changes for setting retirement, clicking cancel')
logger.info("No form changes for setting retirement, clicking cancel")
view.form.cancel.click()
msg = 'Set/remove retirement date was cancelled by the user'
if self.DETAILS_VIEW_CLASS is not None:

if self.DETAILS_VIEW_CLASS:
view = self.create_view(self.DETAILS_VIEW_CLASS, wait='5s')
view.flash.assert_success_message(msg)

view.flash.assert_no_error()

def equal_drift_results(self, drift_section, section, *indexes):
"""Compares drift analysis results of a row specified by it's title text.
Expand Down Expand Up @@ -1011,6 +988,90 @@ def _select_rows(indexes):
class VMCollection(BaseVMCollection):
ENTITY = VM

def retire(self, entities):
"""Select and then retire one or more VMs|Instances from the All VMs|Instances page.

Args:
entities (list): List of one or more InfraVM | Instance objects. This method
is intended to be called from either an InfraVMCollection instance (with a list
of InfraVM objects, or an InstanceCollection instance (with a list of Instance
objects).
"""
view = navigate_to(self, 'All')
if view.paginator.is_displayed:
view.paginator.set_items_per_page(1000)
for e in entities:
view.entities.get_entity(surf_pages=True, name=e.name).ensure_checked()
view.toolbar.lifecycle.item_select('Retire selected items', handle_alert=True)

view = self.create_view(RequestsView)
view.flash.assert_no_error()

def set_retirement_date(self, entities, when=None, offset=None, warn=None):
"""Select and then set the retirement date for one or more VMs|Instances from
the All VMs|Instances page.

Args:
entities: :py:class:`list` of one or more InfraVM | Instance objects. This method
is intended to be called from either an InfraVMCollection instance (with a list
of InfraVM objects, or an InstanceCollection instance (with a list of Instance
objects).
The other arguments are the same as in the :py:class:`VM` method.
"""
changed = False
fill_date = None
fill_offset = None

if when and offset:
raise ValueError("set_retirement_date takes 'when' or 'offset', but not both")
if when and not isinstance(when, (datetime, date)):
raise ValueError("'when' argument must be a datetime object")

if offset:
fill_offset = {k: v
for k, v in offset.items()
if k in ['months', 'weeks', 'days', 'hours']}
elif when:
# format using 4-digit year for form fill
fill_date = when.strftime('%m/%d/%Y %H:%M')
else:
# Clear retirement date with a space in TextInput widget.
# Using space here because empty string does not clear date correctly
fill_date = ' '

# set retirement date from All VMs page
view = navigate_to(self, 'All')
if view.paginator.is_displayed:
view.paginator.set_items_per_page(1000)
for e in entities:
view.entities.get_entity(surf_pages=True, name=e.name).ensure_checked()
view.toolbar.lifecycle.item_select('Set Retirement Dates')

view = self.create_view(RetirementViewWithOffset)
view.form.fill({
'retirement_mode': 'Time Delay from Now' if fill_offset else 'Specific Date and Time'})
view.flush_widget_cache() # since retirement_date is conditional widget

if fill_date:
# two-part fill. widget seems to block warn selection when open.
changed_date = view.form.fill({
'retirement_date': {'datetime_select': fill_date}})
view.title.click() # close datetime widget
changed_warn = view.form.fill({'retirement_warning': warn})
changed = changed_date or changed_warn
elif fill_offset:
changed = view.form.fill({
'retirement_date': fill_offset, 'retirement_warning': warn})

if changed:
view.form.save.click()
else:
logger.info("No form changes for setting retirement, clicking cancel")
view.form.cancel.click()

view = self.create_view(navigator.get_class(self, 'All').VIEW, wait='5s')
view.flash.assert_no_error()


@attr.s
class Template(BaseVM, _TemplateMixin):
Expand Down
10 changes: 10 additions & 0 deletions cfme/fixtures/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,13 @@ def rhel7_minimal(provider):
@pytest.fixture(scope="module")
def rhel7_minimal_modscope(provider):
return _get_template(provider, 'rhel7_minimal')


@pytest.fixture(scope="function")
def s3_template(provider):
return _get_template(provider, 's3_template')


@pytest.fixture(scope="module")
def s3_template_modscope(provider):
return _get_template(provider, 's3_template')
4 changes: 1 addition & 3 deletions cfme/infrastructure/virtual_machines.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ class VmsTemplatesAllView(InfraVmView):
toolbar = View.nested(VMToolbar)
sidebar = View.nested(VmsTemplatesAccordion)
including_entities = View.include(VMEntities, use_parent=True)
pagination = PaginationPane
paginator = PaginationPane()

@property
def is_displayed(self):
Expand Down Expand Up @@ -1452,8 +1452,6 @@ def step(self, *args, **kwargs):
self.view.sidebar.vmstemplates.tree.click_path('All VMs & Templates')

def resetter(self, *args, **kwargs):
if self.view.pagination.is_displayed:
self.view.pagination.set_items_per_page(1000)
self.view.reset_page()


Expand Down
Loading