Skip to content
Snippets Groups Projects
Commit 4ec37633 authored by Janet Goldstein's avatar Janet Goldstein
Browse files

WS-811: Rules for determining capability request state

parent febd6557
No related branches found
No related tags found
1 merge request!679WS-811: Rules for determining capability request state
Pipeline #3773 passed
......@@ -15,7 +15,7 @@
#
# You should have received a copy of the GNU General Public License
# along with Workspaces. If not, see <https://www.gnu.org/licenses/>.
""" Tests for Capability Request """
from typing import Dict
from hypothesis import given
......@@ -216,7 +216,7 @@ def test_determine_state_created(mock_capability_info: CapabilityInfo, request_j
version.capability_request = request
version.capability_request_id = request.id
# Set all versions to Failed state
# Set all versions to Created state
version.state = CapabilityVersionState.Created.name
mock_capability_info.save_entity(version)
......
#
# Copyright (C) 2021 Associated Universities, Inc. Washington DC, USA.
#
# This file is part of NRAO Workspaces.
#
# Workspaces is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Workspaces is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Workspaces. If not, see <https://www.gnu.org/licenses/>.
"""Regression & debugging tests for CapabilityRequestStateSetter"""
from workspaces.capability.enums import CapabilityRequestState, CapabilityVersionState
from workspaces.capability.schema import (
CapabilityRequest,
CapabilityRequestStateSetter,
CapabilityVersion,
)
def test_works_for_one_created():
"""
Given: a capability request with one version
And: that version is in the Created state
When: CapabilityRequestStateSetter determines request state
Then: request state is Created
:return:
"""
request = CapabilityRequest()
request.id = 1
CapabilityVersion(
version_number=1, state=CapabilityVersionState.Created.name, request=request, capability_request_id=request.id
)
CapabilityRequestStateSetter(request).determine_state()
assert request.state == CapabilityRequestState.Created.name
def test_all_versions_same_still_works():
"""
Given: a capability request with several versions all in same state
When: CapabilityRequestStateSetter determines request state
Then: request state corresponds to each version's state
:return:
"""
for state in CapabilityVersionState:
request = CapabilityRequest()
request.id = 1
versions = []
for i in range(4):
version = CapabilityVersion()
version.version_number = i + 1
version.state = state.name
version.request = request
version.capability_request_id = request.id
versions.append(version)
CapabilityRequestStateSetter(request).determine_state()
for version in request.versions:
assert version_state_matches_request_state(version, request)
def test_any_version_complete_still_works():
"""
Given: a capability request with several versions
When: CapabilityRequestStateSetter determines request state
Then: correct request state is set depending on whether or not at least one of its version is Complete
:return:
"""
request = CapabilityRequest(id=1)
assert not CapabilityRequestStateSetter(request).any_version_complete()
CapabilityVersion(
version_number=1, state=CapabilityVersionState.Created.name, request=request, capability_request_id=request.id
)
setter = CapabilityRequestStateSetter(request)
assert not setter.any_version_complete()
setter.determine_state()
assert request.state == CapabilityRequestState.Created.name
request = CapabilityRequest(id=1)
CapabilityVersion(
version_number=1, state=CapabilityVersionState.Complete.name, request=request, capability_request_id=request.id
)
assert CapabilityRequestStateSetter(request).any_version_complete()
request = CapabilityRequest(id=1)
CapabilityVersion(
version_number=1, state=CapabilityVersionState.Created.name, request=request, capability_request_id=request.id
)
CapabilityVersion(
version_number=2, state=CapabilityVersionState.Complete.name, request=request, capability_request_id=request.id
)
setter = CapabilityRequestStateSetter(request)
assert setter.any_version_complete()
request = CapabilityRequest(id=1)
CapabilityVersion(
version_number=1, state=CapabilityVersionState.Created.name, request=request, capability_request_id=request.id
)
CapabilityVersion(
version_number=2, state=CapabilityVersionState.Running.name, request=request, capability_request_id=request.id
)
CapabilityVersion(
version_number=3, state=CapabilityVersionState.Failed.name, request=request, capability_request_id=request.id
)
setter = CapabilityRequestStateSetter(request)
assert not setter.any_version_complete()
setter.determine_state()
assert request.state == CapabilityRequestState.Submitted.name
CapabilityVersion(
version_number=1, state=CapabilityVersionState.Created.name, request=request, capability_request_id=request.id
)
CapabilityVersion(
version_number=2, state=CapabilityVersionState.Running.name, request=request, capability_request_id=request.id
)
CapabilityVersion(
version_number=3, state=CapabilityVersionState.Complete.name, request=request, capability_request_id=request.id
)
setter = CapabilityRequestStateSetter(request)
assert setter.any_version_complete()
def test_still_works_for_one_complete_all_others_failed():
"""
Given: a capability request with several versions, one complete, the rest Failed
When: CapabilityRequestStateSetter determines request state
Then: request state is set to Complete
:return:
"""
request = CapabilityRequest(id=1)
assert not CapabilityRequestStateSetter(request).any_complete_all_others_failed()
CapabilityVersion(
version_number=1, state=CapabilityVersionState.Complete.name, request=request, capability_request_id=request.id
)
assert CapabilityRequestStateSetter(request).any_complete_all_others_failed()
CapabilityRequestStateSetter(request).determine_state()
assert request.state == CapabilityRequestState.Complete.name
request = CapabilityRequest(id=1)
CapabilityVersion(
version_number=1, state=CapabilityVersionState.Failed.name, request=request, capability_request_id=request.id
)
assert not CapabilityRequestStateSetter(request).any_complete_all_others_failed()
request = CapabilityRequest(id=1)
CapabilityVersion(
version_number=1, state=CapabilityVersionState.Failed.name, request=request, capability_request_id=request.id
)
CapabilityVersion(
version_number=2, state=CapabilityVersionState.Complete.name, request=request, capability_request_id=request.id
)
assert CapabilityRequestStateSetter(request).any_complete_all_others_failed()
CapabilityVersion(
version_number=3, state=CapabilityVersionState.Complete.name, request=request, capability_request_id=request.id
)
def version_state_matches_request_state(version: CapabilityVersion, request: CapabilityRequest) -> bool:
"""
Did CapabilityRequestSetter get it right?
:param version:
:param request:
:return:
"""
# a switch()! a switch()! my kingdom for a switch()!
if version.state == CapabilityVersionState.Running.name:
return request.state == CapabilityRequestState.Submitted.name
if version.state == CapabilityRequestState.Created.name:
return request.state == CapabilityRequestState.Created.name
if version.state == CapabilityRequestState.Complete.name:
return request.state == CapabilityRequestState.Complete.name
if version.state == CapabilityRequestState.Failed.name:
return request.state == CapabilityRequestState.Failed.name
raise ValueError(f"Unrecognized capability version state: {version.state}")
## Determining capability request state based on request version(s) states
### Rules for determining request state
- If -any- version is Complete, and -all- other versions are either Failed or Complete, the request is Complete
- If all versions are Failed, the request is failed
- If all versions are Complete, the request is Failed
- If all versions are Created, the request is Created
- Otherwise, it is Submitted
![img.png](img.png)
shared/workspaces/workspaces/capability/img.png

64.3 KiB

......@@ -16,7 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with Workspaces. If not, see <https://www.gnu.org/licenses/>.
# pylint: disable=C0114, C0115, C0116, E0401, R0201, W0613, W0621, W1203
# pylint: disable=C0114, C0115, C0116, E0401, R0201, R0903, W0613, W0621, W1203
from __future__ import annotations
......@@ -339,7 +339,6 @@ class Capability(Base, JSONSerializable):
class Transition(Base):
pattern: str
actions: List[ActionIF]
from_state: State
......@@ -474,27 +473,10 @@ class CapabilityRequest(Base, JSONSerializable):
"""
Determine state of request based on the state of its versions and set it accordingly
RULES:
- If the latest version is complete, the request is complete
- If all versions are failed, the request is failed
- If all versions are created, the request is created
- Otherwise, it is submitted
:return:
"""
version_states = [version.state for version in self.versions]
# Check that the request has versions to base state off of
if len(version_states):
if self.current_version.state == CapabilityVersionState.Complete.name:
# The current version is complete, so the request is complete
self.state = CapabilityRequestState.Complete.name
elif all(state == CapabilityRequestState.Failed.name for state in version_states):
# Request has all failed versions, so it is failed
self.state = CapabilityRequestState.Failed.name
elif all(state == CapabilityRequestState.Created.name for state in version_states):
# Request has no submitted versions, so it is still in the created state
self.state = CapabilityRequestState.Created.name
else:
self.state = CapabilityRequestState.Submitted.name
CapabilityRequestStateSetter(self).determine_state()
def __str__(self):
return f"CapabilityRequest object: {self.__dict__}"
......@@ -770,3 +752,95 @@ class CapabilityExecution(Base, JSONSerializable):
self.state = self.state.get_next_state(message)
# Perform action
previous_state.perform_action(message, self, manager)
class CapabilityRequestStateSetter:
"""Sets request state based on state(s) of its version(s)"""
def __init__(self, request: CapabilityRequest):
self.request = request
self.version_states = [version.state for version in self.request.versions if version.state]
def determine_state(self):
"""
Determine state of request based on state(s) of its version(s).
RULES:
- If -any- version is Complete, and -all- other versions are either Failed or Complete, the request is Complete
- If all versions are Failed, the request is Failed
- If all versions are Complete, the request is Complete
- If all versions are Created, the request is Created
- Otherwise, it is Submitted
Because capabilities depend on the stringified CapabilityRequestState,
here we set the request state to str rather than to state object.
:return:
"""
if not self.version_states:
return
if self.any_version_complete():
if self.all_versions_same() or self.any_complete_all_others_failed():
self.request.state = CapabilityRequestState.Complete.name
elif self.all_versions_in_state(CapabilityVersionState.Failed.name):
self.request.state = CapabilityRequestState.Failed.name
elif self.all_versions_in_state(CapabilityVersionState.Created.name):
self.request.state = CapabilityRequestState.Created.name
elif self.all_versions_in_state(CapabilityVersionState.Running):
self.request.state = CapabilityRequestState.Submitted.name
else:
self.evaluate_mixed_versions()
def evaluate_mixed_versions(self):
"""
Determine request state if no version is either Failed or Complete.
:return:
"""
if CapabilityVersionState.Running.name in self.version_states:
self.request.state = CapabilityRequestState.Submitted.name
elif CapabilityVersionState.Created.name in self.version_states:
self.request.state = CapabilityRequestState.Created.name
def all_versions_in_state(self, state: CapabilityVersionState) -> bool:
"""
Given a request with multiple versions, is every version in this state?
:param state:
:return:
"""
if self.all_versions_same():
return self.request.versions[0].state == state
def all_versions_same(self) -> bool:
"""
We've got a request with more than one version.
Are all the versions' states identical?
:return:
"""
return len(set(self.version_states)) == 1
def any_version_complete(self) -> bool:
"""
Of all the versions in this request, is at least one of them complete?
:return:
"""
return any(state == CapabilityVersionState.Complete.name for state in self.version_states)
def any_complete_all_others_failed(self) -> bool:
"""
Given a request with at least one Complete version, are all the others Failed?
:return:
"""
if self.any_version_complete():
for state in self.version_states:
if state not in [CapabilityRequestState.Failed.name, CapabilityRequestState.Complete.name]:
return False
return True
return False
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment