Skip to content

Commit

Permalink
Merge pull request #3 from nazarenof/stop_random_instance
Browse files Browse the repository at this point in the history
Add stop_random_instance
  • Loading branch information
Lawouach authored Dec 8, 2018
2 parents b855242 + ccd053a commit 8944b8a
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 73 deletions.
12 changes: 8 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
Expand Down Expand Up @@ -103,7 +104,10 @@ ENV/
.vscode/
junit-test-results.xml

chaosoci
experiment*
journal.json
.gitignore
# PyCharm / Idea
.idea/
*.iml

# vim
*.swp
*.json
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

[Unreleased]: https://github.com/chaostoolkit-incubator/chaostoolkit-oci/compare/0.1.0...HEAD

- Stop random instance, allowing to do so in terms of the requested filters.

### Added

- Probe to count the number of instances in the compartment with the possibility of added filters.
Expand Down
52 changes: 46 additions & 6 deletions chaosoci/compute/actions.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,66 @@
# -*- coding: utf-8 -*-
from oci.core import ComputeClient
from random import choice
from typing import Any, Dict, List

from chaoslib.exceptions import ActivityFailed
from chaoslib.types import Configuration, Secrets
from oci.config import from_file
from oci.core import ComputeClient

from chaosoci import oci_client
from chaosoci.types import OCIResponse

from .common import filter_instances, get_instances

__all__ = ["stop_instance"]
__all__ = ["stop_instance", "stop_random_instance"]


def stop_instance(instance_id: str, force: bool = False,
configuration: Configuration = None,
secrets: Secrets = None) -> OCIResponse:
"""Stop a given Compute instance."""
action = "SOFTSTOP"
client = oci_client(ComputeClient, configuration, secrets,
skip_deserialization=True)

if force is True:
action = "STOP"
action = "STOP" if force else "SOFTSTOP"
ret = client.instance_action(instance_id=instance_id, action=action).data

return ret


def stop_random_instance(filters: List[Dict[str, Any]],
compartment_id: str = None,
force: bool = False,
configuration: Configuration = None,
secrets: Secrets = None) -> OCIResponse:
"""
Stop a a random compute instance within a given compartment.
If filters are provided, the scope will be reduced to those instances
matching the filters.
Please refer to: https://oracle-cloud-infrastructure-python-sdk.readthedocs.io/en/latest/api/core/models/oci.core.models.Instance.html#oci.core.models.Instance
for details on the available filters under the 'parameters' section.
""" # noqa: E501
client = oci_client(ComputeClient, configuration, secrets,
skip_deserialization=False)

action = "STOP" if force else "SOFTSTOP"

compartment_id = compartment_id or from_file().get('compartment')
if compartment_id is None:
raise ActivityFailed('We have not been able to find a compartment,'
' without one, we cannot continue.')

instances = get_instances(client, compartment_id)

filters = filters or None
if filters is not None:
instances = filter_instances(instances, filters=filters)

instance_id = choice(instances).id

ret = client.instance_action(instance_id=instance_id, action=action)
s_client = oci_client(ComputeClient, configuration, secrets,
skip_deserialization=True)
ret = s_client.instance_action(instance_id=instance_id, action=action)

return ret.data
54 changes: 54 additions & 0 deletions chaosoci/compute/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from typing import Any, Dict, List

from chaoslib.exceptions import ActivityFailed
from oci.core import ComputeClient

from chaosoci.types import OCIInstance

__all__ = ["get_instances", "filter_instances"]


def get_instances(client: ComputeClient = None,
compartment_id: str = None) -> List[OCIInstance]:
"""Return a complete, unfiltered list of instances in the compartment."""
instances = []

instances_raw = client.list_instances(compartment_id=compartment_id)
instances.extend(instances_raw.data)
while instances_raw.has_next_page:
instances_raw = client.list_instances(compartment_id=compartment_id,
page=instances_raw.next_page)
instances.extend(instances_raw.data)

return instances


def filter_instances(instances: List[OCIInstance] = None,
filters: Dict[str, Any] = None) -> List[OCIInstance]:
"""Return only those instances that match the filters provided."""
instances = instances or None

if instances is None:
raise ActivityFailed('No instances were found.')

filters_set = {x for x in filters}
available_filters_set = {x for x in instances[0].attribute_map}

# Partial filtering may return instances we do not want. We avoid it.
if not filters_set.issubset(available_filters_set):
raise ActivityFailed('Some of the chosen filters were not found,'
' we cannot continue.')

# Walk the instances and find those that match the given filters.
filtered = []
for instance in instances:
sentinel = True
for attr, val in filters.items():
if val != getattr(instance, attr, None):
sentinel = False
break

if sentinel:
filtered.append(instance)

return filtered
68 changes: 12 additions & 56 deletions chaosoci/compute/probes.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
# -*- coding: utf-8 -*-
from typing import Any, List, Dict
from typing import Any, Dict, List

from oci.core import ComputeClient
from oci.config import from_file

from chaoslib.types import Configuration, Secrets
from chaoslib.exceptions import ActivityFailed
from chaoslib.types import Configuration, Secrets
from oci.config import from_file
from oci.core import ComputeClient

from chaosoci import oci_client

from .common import filter_instances, get_instances

__all__ = ['count_instances']


def count_instances(filters: List[Dict[str, Any]], compartment_id: str = None,
configuration: Configuration = None,
secrets: Secrets = None) -> int:
""" Return the number of instances in accordance with the given filters.
"""
Return the number of instances in accordance with the given filters.
Please refer to: https://oracle-cloud-infrastructure-python-sdk.readthedocs.io/en/latest/api/core/models/oci.core.models.Instance.html#oci.core.models.Instance
Please refer to: https://oracle-cloud-infrastructure-python-sdk.readthedocs.io/en/latest/api/core/models/oci.core.models.Instance.html#oci.core.models.Instance
for details on the available filters under the 'parameters' section.
for details on the available filters under the 'parameters' section.
""" # noqa: E501
compartment_id = compartment_id or from_file().get('compartment')

Expand All @@ -31,55 +33,9 @@ def count_instances(filters: List[Dict[str, Any]], compartment_id: str = None,
skip_deserialization=False)

filters = filters or None
instances = _get_instances(client, compartment_id)
instances = get_instances(client, compartment_id)

if filters is not None:
return len(_filter_instances(instances, filters=filters))
return len(filter_instances(instances, filters=filters))

return len(instances)


def _get_instances(client: ComputeClient = None,
compartment_id: str = None) -> List:
"""Return a complete, unfiltered list of instances in the compartment."""
instances = []

instances_raw = client.list_instances(compartment_id=compartment_id)
instances.extend(instances_raw.data)
while instances_raw.has_next_page:
instances_raw = client.list_instances(compartment_id=compartment_id,
page=instances_raw.next_page)
instances.extend(instances_raw.data)

return instances


def _filter_instances(instances: List = None,
filters: Dict[str, Any] = None) -> List:
"""Return only those instances that match the filters provided."""
instances = instances or None

if instances is None:
raise ActivityFailed('No instances were found.')

filters_set = {x for x in filters}
available_filters_set = {x for x in instances[0].attribute_map}

# Partial filtering may return instances we do not want. We avoid it.
if not filters_set.issubset(available_filters_set):
raise ActivityFailed('Some of the chosen filters were not found,'
' we cannot continue.')

# Walk the instances and find those that match the given filters.
filtered = []
for instance in instances:
sentinel = True
for attr, val in filters.items():
if val != getattr(instance, attr, None):
sentinel = False
break

if sentinel:
filtered.append(instance)

return filtered
4 changes: 3 additions & 1 deletion chaosoci/types.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# -*- coding: utf-8 -*-
from typing import Any, Dict
from oci.core.models.instance import Instance

__all__ = ["OCIResponse"]
__all__ = ["OCIResponse", "OCIInstance"]

# really dependent on the type of resource called
OCIResponse = Dict[str, Any]
OCIInstance = Instance
46 changes: 44 additions & 2 deletions tests/compute/test_compute_actions.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from unittest.mock import MagicMock, patch

from chaosoci.compute.actions import stop_instance
from chaosoci.compute.actions import stop_instance, stop_random_instance


@patch('chaosoci.compute.actions.oci_client', autospec=True)
Expand All @@ -10,6 +10,48 @@ def test_stop_instance(oci_client):
oci_client.return_value = compute_client
inst_id = "i-1234567890abcdef0"
action = "SOFTSTOP"
stop_instance(inst_id, action)
stop_instance(inst_id, force=False)
compute_client.instance_action.assert_called_with(instance_id=inst_id,
action=action)


@patch('chaosoci.compute.actions.filter_instances', autospec=True)
@patch('chaosoci.compute.actions.get_instances', autospec=True)
@patch('chaosoci.compute.actions.oci_client', autospec=True)
def test_filter_stop_random_instances(oci_client, get_instances,
filter_instances):
instance = MagicMock()
instance.id = "i-1234567890abcdef0"

c_id = "ocid1.compartment.oc1..oadsocmof6r6ksovxmda44ikwxje7xxu"
filters = [{'display_name': 'random_name', 'region': 'uk-london-1'}]

instances = [instance, instance, instance, instance]
filter_instances.return_value = instances

stop_random_instance(filters=filters, compartment_id=c_id)
filter_instances.assert_called_with(instances=get_instances(oci_client,
c_id),
filters=filters)


@patch('chaosoci.compute.actions.filter_instances', autospec=True)
@patch('chaosoci.compute.actions.get_instances', autospec=True)
@patch('chaosoci.compute.actions.oci_client', autospec=True)
def test_stop_random_instances(oci_client, get_instances, filter_instances):
compute_client = MagicMock()
instance = MagicMock()
oci_client.return_value = compute_client

c_id = "ocid1.compartment.oc1..oadsocmof6r6ksovxmda44ikwxje7xxu"
filters = [{'display_name': 'random_name', 'region': 'uk-london-1'}]
action = "SOFTSTOP"

instance.id = "i-1234567890abcdef0"
instances = [instance, instance, instance, instance]
filter_instances.return_value = instances

stop_random_instance(filters=filters, compartment_id=c_id)

compute_client.instance_action.assert_called_with(instance_id=instance.id,
action=action)
12 changes: 8 additions & 4 deletions tests/compute/test_compute_probes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,32 @@
from chaosoci.compute.probes import count_instances


@patch('chaosoci.compute.probes._filter_instances', autospec=True)
@patch('chaosoci.compute.probes._get_instances', autospec=True)
@patch('chaosoci.compute.probes.filter_instances', autospec=True)
@patch('chaosoci.compute.probes.get_instances', autospec=True)
@patch('chaosoci.compute.probes.oci_client', autospec=True)
def test_count_instances(oci_client, get_instances, filter_instances):
compute_client = MagicMock()
oci_client.return_value = compute_client

c_id = "ocid1.compartment.oc1..oadsocmof6r6ksovxmda44ikwxje7xxu"
filters = [{'display_name': 'random_name', 'region': 'uk-london-1'}]

count_instances(filters=filters, compartment_id=c_id)
filter_instances.assert_called_with(instances=get_instances(oci_client,
c_id),
filters=filters)


@patch('chaosoci.compute.probes._filter_instances', autospec=True)
@patch('chaosoci.compute.probes._get_instances', autospec=True)
@patch('chaosoci.compute.probes.filter_instances', autospec=True)
@patch('chaosoci.compute.probes.get_instances', autospec=True)
@patch('chaosoci.compute.probes.oci_client', autospec=True)
def test_count_instances_ret_int(oci_client, get_instances, filter_instances):
compute_client = MagicMock()
oci_client.return_value = compute_client

filter_instances.return_value = ['one', 'two', 'three']
c_id = "ocid1.compartment.oc1..oadsocmof6r6ksovxmda44ikwxje7xxu"
filters = [{'display_name': 'random_name', 'region': 'uk-london-1'}]

n = count_instances(filters=filters, compartment_id=c_id)
T().assertEqual(n, 3)

0 comments on commit 8944b8a

Please sign in to comment.