Merge "Abort VM Status flow when VM(s) go to ERROR state"
This commit is contained in:
commit
b9290138bf
|
@ -141,3 +141,11 @@ class NodeAlreadyExists(Conflict):
|
|||
|
||||
class ConfigurationError(CueException):
|
||||
message = _("Configuration Error")
|
||||
|
||||
|
||||
class VmBuildingException(CueException):
|
||||
message = _("VM is in building state")
|
||||
|
||||
|
||||
class VmErrorException(CueException):
|
||||
message = _("VM is not in a building state")
|
||||
|
|
|
@ -15,10 +15,11 @@
|
|||
|
||||
from oslo_config import cfg
|
||||
import taskflow.patterns.linear_flow as linear_flow
|
||||
import taskflow.retry as retry
|
||||
|
||||
import cue.client as client
|
||||
from cue.common import exception as cue_exceptions
|
||||
from cue.db.sqlalchemy import models
|
||||
import cue.taskflow.retry.exception_times as retry
|
||||
import cue.taskflow.task as cue_tasks
|
||||
import os_tasklib.common as os_common
|
||||
import os_tasklib.neutron as neutron
|
||||
|
@ -132,26 +133,32 @@ def create_cluster_node(cluster_id, node_number, node_id, graph_flow,
|
|||
name="extract vm id %s" % node_name,
|
||||
rebind={'vm_info': "vm_info_%d" % node_number},
|
||||
provides="vm_id_%d" % node_number)
|
||||
|
||||
graph_flow.add(get_vm_id)
|
||||
graph_flow.link(create_vm, get_vm_id)
|
||||
|
||||
retry_count = CONF.flow_options.create_cluster_node_vm_active_retry_count
|
||||
check_vm_active = linear_flow.Flow(
|
||||
name="wait for VM active state %s" % node_name,
|
||||
retry=retry.Times(retry_count, revert_all=True))
|
||||
retry=retry.ExceptionTimes(
|
||||
revert_exception_list=[cue_exceptions.VmErrorException],
|
||||
attempts=retry_count,
|
||||
revert_all=True)
|
||||
)
|
||||
|
||||
check_vm_active.add(
|
||||
nova.GetVmStatus(
|
||||
os_client=client.nova_client(),
|
||||
name="get vm %s" % node_name,
|
||||
rebind={'nova_vm_id': "vm_id_%d" % node_number},
|
||||
provides="vm_status_%d" % node_number),
|
||||
os_common.CheckFor(
|
||||
cue_tasks.CheckForVmStatus(
|
||||
name="check vm status %s" % node_name,
|
||||
details="waiting for ACTIVE VM status",
|
||||
rebind={'check_var': "vm_status_%d" % node_number},
|
||||
check_value='ACTIVE',
|
||||
retry_delay_seconds=10),
|
||||
)
|
||||
|
||||
graph_flow.add(check_vm_active)
|
||||
graph_flow.link(get_vm_id, check_vm_active)
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from cue.taskflow.retry.exception_times import ExceptionTimes # noqa
|
|
@ -0,0 +1,54 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from oslo_log import log as logging
|
||||
import taskflow.retry as retry
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ExceptionTimes(retry.Times):
|
||||
"""Retries subflow given number of times. Returns attempt number.
|
||||
|
||||
:param attempts: number of attempts to retry the associated subflow
|
||||
before giving up
|
||||
:type attempts: int
|
||||
:param revert_all: when provided this will cause the full flow to revert
|
||||
when the number of attempts that have been tried
|
||||
has been reached (when false, it will only locally
|
||||
revert the associated subflow)
|
||||
:type revert_all: bool
|
||||
Further arguments are interpreted as defined in the
|
||||
:py:class:`~taskflow.atom.Atom` constructor.
|
||||
"""
|
||||
|
||||
def __init__(self, revert_exception_list=None, **kwargs):
|
||||
super(ExceptionTimes, self).__init__(**kwargs)
|
||||
if revert_exception_list:
|
||||
self._revert_exception_list = revert_exception_list
|
||||
else:
|
||||
self._revert_exception_list = []
|
||||
|
||||
def on_failure(self, history, *args, **kwargs):
|
||||
|
||||
(owner, outcome) = history.outcomes_iter(len(history) - 1).next()
|
||||
|
||||
if type(outcome.exception) in self._revert_exception_list:
|
||||
return self._revert_action
|
||||
|
||||
return super(ExceptionTimes, self).on_failure(history, args, kwargs)
|
||||
|
||||
def execute(self, history, *args, **kwargs):
|
||||
return len(history) + 1
|
|
@ -13,10 +13,11 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from cue.taskflow.task.check_for_vm_status import CheckForVmStatus # noqa
|
||||
from cue.taskflow.task.cluster_node_userdata import ClusterNodeUserData # noqa
|
||||
from cue.taskflow.task.create_endpoint_task import CreateEndpoint # noqa
|
||||
from cue.taskflow.task.get_node import GetNode # noqa
|
||||
from cue.taskflow.task.get_rabbit_cluster_status import GetRabbitClusterStatus # noqa
|
||||
from cue.taskflow.task.update_cluster_record_task import UpdateClusterRecord # noqa
|
||||
from cue.taskflow.task.update_endpoints_record_task import UpdateEndpointsRecord # noqa
|
||||
from cue.taskflow.task.update_node_record_task import UpdateNodeRecord # noqa
|
||||
from cue.taskflow.task.update_endpoints_record_task import UpdateEndpointsRecord # noqa
|
||||
from cue.taskflow.task.update_node_record_task import UpdateNodeRecord # noqa
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import time
|
||||
|
||||
import taskflow.task as task
|
||||
|
||||
from cue.common import exception as cue_exceptions
|
||||
|
||||
|
||||
class CheckForVmStatus(task.Task):
|
||||
def __init__(self,
|
||||
check_value_building='BUILD',
|
||||
check_value_active='ACTIVE',
|
||||
retry_delay_seconds=None,
|
||||
retry_delay_ms=None,
|
||||
name=None,
|
||||
details=None,
|
||||
**kwargs):
|
||||
super(CheckForVmStatus, self).__init__(name=name, **kwargs)
|
||||
|
||||
self.check_value_building = check_value_building
|
||||
self.check_value_active = check_value_active
|
||||
self.sleep_time = 0
|
||||
self.details = details
|
||||
if retry_delay_seconds:
|
||||
self.sleep_time = retry_delay_seconds
|
||||
|
||||
if retry_delay_ms:
|
||||
self.sleep_time += retry_delay_ms / 1000.0
|
||||
|
||||
def execute(self, check_var, **kwargs):
|
||||
error_string = "expected %s, got %s" % (self.check_value_active,
|
||||
check_var)
|
||||
if self.details is not None:
|
||||
error_string += ", message: %s" % self.details
|
||||
|
||||
if check_var == self.check_value_building:
|
||||
raise cue_exceptions.VmBuildingException(error_string)
|
||||
elif check_var != self.check_value_active:
|
||||
raise cue_exceptions.VmErrorException(error_string)
|
||||
|
||||
def revert(self, check_var, *args, **kwargs):
|
||||
if self.sleep_time != 0:
|
||||
time.sleep(self.sleep_time)
|
|
@ -13,6 +13,7 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
import uuid
|
||||
|
||||
import novaclient.exceptions as nova_exc
|
||||
|
@ -87,8 +88,7 @@ class VmStatusDetails(object):
|
|||
|
||||
:param statuses: list of statuses
|
||||
"""
|
||||
for status in statuses:
|
||||
VmStatusDetails.vm_status_list.append(status)
|
||||
VmStatusDetails.vm_status_list = copy.deepcopy(statuses)
|
||||
|
||||
@staticmethod
|
||||
def get_status():
|
||||
|
|
|
@ -16,8 +16,12 @@
|
|||
import uuid
|
||||
|
||||
from neutronclient.common import exceptions as neutron_exceptions
|
||||
from oslo_config import cfg
|
||||
from taskflow import engines
|
||||
import taskflow.exceptions as taskflow_exc
|
||||
|
||||
from cue import client
|
||||
from cue.common import exception as cue_exceptions
|
||||
from cue.db.sqlalchemy import models
|
||||
from cue import objects
|
||||
from cue.taskflow.flow import create_cluster
|
||||
|
@ -26,8 +30,8 @@ from cue.tests.functional.fixtures import neutron
|
|||
from cue.tests.functional.fixtures import nova
|
||||
from cue.tests.functional.fixtures import urllib2_fixture
|
||||
|
||||
from taskflow import engines
|
||||
import taskflow.exceptions as taskflow_exc
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class CreateClusterTests(base.FunctionalTestCase):
|
||||
|
@ -151,6 +155,175 @@ class CreateClusterTests(base.FunctionalTestCase):
|
|||
self.assertEqual(expected_management_ip, actual_management_ip,
|
||||
"invalid management ip")
|
||||
|
||||
def test_create_cluster_nova_error(self):
|
||||
flow_store = {
|
||||
"tenant_id": str(self.valid_network['tenant_id']),
|
||||
"image": self.valid_image.id,
|
||||
"flavor": self.valid_flavor.id,
|
||||
"port": self.port,
|
||||
"context": self.context.to_dict(),
|
||||
"erlang_cookie": str(uuid.uuid4()),
|
||||
"default_rabbit_user": 'rabbit',
|
||||
"default_rabbit_pass": str(uuid.uuid4()),
|
||||
}
|
||||
|
||||
cluster_values = {
|
||||
"project_id": self.context.tenant_id,
|
||||
"name": "RabbitCluster",
|
||||
"network_id": self.valid_network['id'],
|
||||
"flavor": "1",
|
||||
"size": 3,
|
||||
}
|
||||
|
||||
new_cluster = objects.Cluster(**cluster_values)
|
||||
new_cluster.create(self.context)
|
||||
|
||||
nodes = objects.Node.get_nodes_by_cluster_id(self.context,
|
||||
new_cluster.id)
|
||||
|
||||
CONF.flow_options.create_cluster_node_vm_active_retry_count = 3
|
||||
|
||||
# configure custom vm_status list
|
||||
nova.VmStatusDetails.set_vm_status(['ACTIVE',
|
||||
'ERROR',
|
||||
'BUILD',
|
||||
'BUILD'])
|
||||
|
||||
node_ids = []
|
||||
for node in nodes:
|
||||
node_ids.append(node.id)
|
||||
|
||||
flow = create_cluster(new_cluster.id,
|
||||
node_ids,
|
||||
self.valid_network['id'],
|
||||
self.management_network['id'])
|
||||
|
||||
try:
|
||||
engines.run(flow, store=flow_store)
|
||||
except taskflow_exc.WrappedFailure as err:
|
||||
self.assertEqual(3, len(err._causes))
|
||||
exc_list = [type(c.exception) for c in err._causes]
|
||||
self.assertEqual(sorted([cue_exceptions.VmErrorException,
|
||||
cue_exceptions.VmBuildingException,
|
||||
cue_exceptions.VmBuildingException]),
|
||||
sorted(exc_list))
|
||||
except Exception as e:
|
||||
self.assertEqual(taskflow_exc.WrappedFailure, type(e))
|
||||
else:
|
||||
self.fail("Expected taskflow_exc.WrappedFailure exception.")
|
||||
|
||||
def test_create_cluster_max_retries_multi_node_single_retry(self):
|
||||
flow_store = {
|
||||
"tenant_id": str(self.valid_network['tenant_id']),
|
||||
"image": self.valid_image.id,
|
||||
"flavor": self.valid_flavor.id,
|
||||
"port": self.port,
|
||||
"context": self.context.to_dict(),
|
||||
"erlang_cookie": str(uuid.uuid4()),
|
||||
"default_rabbit_user": 'rabbit',
|
||||
"default_rabbit_pass": str(uuid.uuid4()),
|
||||
}
|
||||
|
||||
cluster_values = {
|
||||
"project_id": self.context.tenant_id,
|
||||
"name": "RabbitCluster",
|
||||
"network_id": self.valid_network['id'],
|
||||
"flavor": "1",
|
||||
"size": 3,
|
||||
}
|
||||
|
||||
new_cluster = objects.Cluster(**cluster_values)
|
||||
new_cluster.create(self.context)
|
||||
|
||||
nodes = objects.Node.get_nodes_by_cluster_id(self.context,
|
||||
new_cluster.id)
|
||||
|
||||
# Todo: Raise the retry count once the fixture timeout issue
|
||||
# is resolved
|
||||
CONF.flow_options.create_cluster_node_vm_active_retry_count = 1
|
||||
|
||||
# configure custom vm_status list
|
||||
nova.VmStatusDetails.set_vm_status(['BUILD',
|
||||
'BUILD',
|
||||
'BUILD',
|
||||
'BUILD'])
|
||||
|
||||
node_ids = []
|
||||
for node in nodes:
|
||||
node_ids.append(node.id)
|
||||
|
||||
flow = create_cluster(new_cluster.id,
|
||||
node_ids,
|
||||
self.valid_network['id'],
|
||||
self.management_network['id'])
|
||||
|
||||
try:
|
||||
engines.run(flow, store=flow_store)
|
||||
except taskflow_exc.WrappedFailure as err:
|
||||
self.assertEqual(3, len(err._causes))
|
||||
exc_list = [type(c.exception) for c in err._causes]
|
||||
self.assertEqual([cue_exceptions.VmBuildingException,
|
||||
cue_exceptions.VmBuildingException,
|
||||
cue_exceptions.VmBuildingException],
|
||||
exc_list)
|
||||
except Exception as e:
|
||||
self.assertEqual(taskflow_exc.WrappedFailure, type(e))
|
||||
else:
|
||||
self.fail("Expected taskflow_exc.WrappedFailure exception.")
|
||||
|
||||
def test_create_cluster_max_retries_single_node(self):
|
||||
flow_store = {
|
||||
"tenant_id": str(self.valid_network['tenant_id']),
|
||||
"image": self.valid_image.id,
|
||||
"flavor": self.valid_flavor.id,
|
||||
"port": self.port,
|
||||
"context": self.context.to_dict(),
|
||||
"erlang_cookie": str(uuid.uuid4()),
|
||||
"default_rabbit_user": 'rabbit',
|
||||
"default_rabbit_pass": str(uuid.uuid4()),
|
||||
}
|
||||
|
||||
cluster_values = {
|
||||
"project_id": self.context.tenant_id,
|
||||
"name": "RabbitCluster",
|
||||
"network_id": self.valid_network['id'],
|
||||
"flavor": "1",
|
||||
"size": 1,
|
||||
}
|
||||
|
||||
new_cluster = objects.Cluster(**cluster_values)
|
||||
new_cluster.create(self.context)
|
||||
|
||||
nodes = objects.Node.get_nodes_by_cluster_id(self.context,
|
||||
new_cluster.id)
|
||||
|
||||
CONF.flow_options.create_cluster_node_vm_active_retry_count = 3
|
||||
|
||||
# configure custom vm_status list
|
||||
nova.VmStatusDetails.set_vm_status(['BUILD',
|
||||
'BUILD',
|
||||
'BUILD',
|
||||
'BUILD'])
|
||||
|
||||
node_ids = []
|
||||
for node in nodes:
|
||||
node_ids.append(node.id)
|
||||
|
||||
flow = create_cluster(new_cluster.id,
|
||||
node_ids,
|
||||
self.valid_network['id'],
|
||||
self.management_network['id'])
|
||||
|
||||
self.assertRaises(cue_exceptions.VmBuildingException,
|
||||
engines.run, flow, store=flow_store)
|
||||
|
||||
def test_create_cluster_max_retries_multi_node_multi_retry(self):
|
||||
#Todo - This test is stubbed due to issues with the fixture timeout
|
||||
# configuration, which interrupts the this test. The timeout needs
|
||||
# to be solved and this test needs to be implemented to give full
|
||||
# confidence in the retry feature.
|
||||
pass
|
||||
|
||||
def test_create_cluster_overlimit(self):
|
||||
vm_list = self.nova_client.servers.list()
|
||||
port_list = self.neutron_client.list_ports()
|
||||
|
@ -296,4 +469,4 @@ class CreateClusterTests(base.FunctionalTestCase):
|
|||
def tearDown(self):
|
||||
for vm_id in self.new_vm_list:
|
||||
self.nova_client.servers.delete(vm_id)
|
||||
super(CreateClusterTests, self).tearDown()
|
||||
super(CreateClusterTests, self).tearDown()
|
|
@ -84,9 +84,9 @@ class GetVmStatusTests(base.FunctionalTestCase):
|
|||
"""
|
||||
# configure custom vm_status list
|
||||
nova.VmStatusDetails.set_vm_status(['ACTIVE',
|
||||
'BUILDING',
|
||||
'BUILDING',
|
||||
'BUILDING'])
|
||||
'BUILD',
|
||||
'BUILD',
|
||||
'BUILD'])
|
||||
|
||||
# create flow with "GetVmStatus" task
|
||||
self.flow = linear_flow.Flow('wait for vm to become active',
|
||||
|
@ -125,11 +125,11 @@ class GetVmStatusTests(base.FunctionalTestCase):
|
|||
failure.
|
||||
"""
|
||||
# configure custom vm_status list
|
||||
nova.VmStatusDetails.set_vm_status(['BUILDING',
|
||||
'BUILDING',
|
||||
'BUILDING',
|
||||
'BUILDING',
|
||||
'BUILDING',
|
||||
nova.VmStatusDetails.set_vm_status(['BUILD',
|
||||
'BUILD',
|
||||
'BUILD',
|
||||
'BUILD',
|
||||
'BUILD',
|
||||
'ERROR',
|
||||
'ERROR'])
|
||||
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
|
||||
from cue.common import exception as cue_exceptions
|
||||
import cue.taskflow.task.check_for_vm_status as check_for_vm_status
|
||||
from cue.tests.unit import base
|
||||
|
||||
|
||||
class TaskflowCheckVmStatusTest(base.UnitTestCase):
|
||||
def setUp(self):
|
||||
super(TaskflowCheckVmStatusTest, self).setUp()
|
||||
|
||||
def tearDown(self):
|
||||
super(TaskflowCheckVmStatusTest, self).tearDown()
|
||||
|
||||
@mock.patch('time.sleep')
|
||||
def test_check_vm_status_ms(self, mock_sleep):
|
||||
check_task = check_for_vm_status.CheckForVmStatus(
|
||||
name="check vm status",
|
||||
details="waiting for ACTIVE VM status",
|
||||
retry_delay_ms=1000,
|
||||
)
|
||||
check_task.revert('BUILD')
|
||||
|
||||
self.assertEqual(1.0, mock_sleep.call_args[0][0])
|
||||
|
||||
def test_check_vm_status_no_details(self):
|
||||
check_task = check_for_vm_status.CheckForVmStatus(
|
||||
name="check vm status",
|
||||
)
|
||||
try:
|
||||
check_task.execute('BUILD')
|
||||
except cue_exceptions.VmBuildingException as err:
|
||||
self.assertEqual(cue_exceptions.VmBuildingException.message,
|
||||
err.message)
|
||||
else:
|
||||
self.fail("Expected VmBuildingException")
|
|
@ -0,0 +1,44 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import taskflow.retry as tf_retry
|
||||
|
||||
from cue.common import exception as cue_exceptions
|
||||
import cue.taskflow.retry.exception_times as retry
|
||||
from cue.tests.unit import base
|
||||
|
||||
|
||||
class TaskflowExceptionTimesTest(base.UnitTestCase):
|
||||
def setUp(self):
|
||||
super(TaskflowExceptionTimesTest, self).setUp()
|
||||
|
||||
def tearDown(self):
|
||||
super(TaskflowExceptionTimesTest, self).tearDown()
|
||||
|
||||
def test_revert_action(self):
|
||||
|
||||
retry_exception_times = retry.ExceptionTimes(
|
||||
revert_exception_list=[cue_exceptions.VmErrorException],
|
||||
attempts=10,
|
||||
revert_all=False)
|
||||
self.assertEqual(tf_retry.REVERT, retry_exception_times._revert_action)
|
||||
|
||||
def test_revert_action_empty_exception_list(self):
|
||||
|
||||
retry_exception_times = retry.ExceptionTimes(
|
||||
revert_exception_list=None,
|
||||
attempts=10,
|
||||
revert_all=False)
|
||||
self.assertEqual([], retry_exception_times._revert_exception_list)
|
|
@ -13,8 +13,8 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from os_tasklib.common.assert_task import Assert # noqa
|
||||
from os_tasklib.common.check_for import CheckFor # noqa
|
||||
from os_tasklib.common.lambda_task import Lambda # noqa
|
||||
from os_tasklib.common.map_task import Map # noqa
|
||||
from os_tasklib.common.reduce_task import Reduce # noqa
|
||||
from os_tasklib.common.assert_task import Assert # noqa
|
||||
from os_tasklib.common.check_for import CheckFor # noqa
|
||||
from os_tasklib.common.lambda_task import Lambda # noqa
|
||||
from os_tasklib.common.map_task import Map # noqa
|
||||
from os_tasklib.common.reduce_task import Reduce # noqa
|
||||
|
|
Loading…
Reference in New Issue