Skip to content

Commit 3d6437b

Browse files
authored
Generic Spark Integration (#101)
* Generic Spark Integration * Image * PR comments * PR comments * Separate task-type for generic-spark * PR comments
1 parent 24ceff1 commit 3d6437b

7 files changed

Lines changed: 288 additions & 4 deletions

File tree

flytekit/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
from __future__ import absolute_import
22
import flytekit.plugins
33

4-
__version__ = '0.7.0b3'
4+
__version__ = '0.7.0b4'
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
from __future__ import absolute_import
2+
3+
try:
4+
from inspect import getfullargspec as _getargspec
5+
except ImportError:
6+
from inspect import getargspec as _getargspec
7+
8+
from flytekit import __version__
9+
import sys as _sys
10+
import six as _six
11+
from flytekit.common.tasks import task as _base_tasks
12+
from flytekit.common.types import helpers as _helpers, primitives as _primitives
13+
14+
from flytekit.models import literals as _literal_models, task as _task_models
15+
from google.protobuf.json_format import MessageToDict as _MessageToDict
16+
from flytekit.common import interface as _interface
17+
from flytekit.common.exceptions import user as _user_exceptions
18+
from flytekit.common.exceptions import scopes as _exception_scopes
19+
20+
from flytekit.configuration import internal as _internal_config
21+
22+
input_types_supported = { _primitives.Integer,
23+
_primitives.Boolean,
24+
_primitives.Float,
25+
_primitives.String,
26+
_primitives.Datetime,
27+
_primitives.Timedelta,
28+
}
29+
30+
31+
class SdkGenericSparkTask( _base_tasks.SdkTask):
32+
"""
33+
This class includes the additional logic for building a task that executes as a Spark Job.
34+
35+
"""
36+
def __init__(
37+
self,
38+
task_type,
39+
discovery_version,
40+
retries,
41+
interruptible,
42+
task_inputs,
43+
deprecated,
44+
discoverable,
45+
timeout,
46+
spark_type,
47+
main_class,
48+
main_application_file,
49+
spark_conf,
50+
hadoop_conf,
51+
environment,
52+
):
53+
"""
54+
:param Text task_type: string describing the task type
55+
:param Text discovery_version: string describing the version for task discovery purposes
56+
:param int retries: Number of retries to attempt
57+
:param bool interruptible: Whether or not task is interruptible
58+
:param Text deprecated:
59+
:param bool discoverable:
60+
:param datetime.timedelta timeout:
61+
:param Text spark_type: Type of Spark Job: Scala/Java
62+
:param Text main_class: Main class to execute for Scala/Java jobs
63+
:param Text main_application_file: Main application file
64+
:param dict[Text,Text] spark_conf:
65+
:param dict[Text,Text] hadoop_conf:
66+
:param dict[Text,Text] environment: [optional] environment variables to set when executing this task.
67+
"""
68+
69+
spark_job = _task_models.SparkJob(
70+
spark_conf=spark_conf,
71+
hadoop_conf=hadoop_conf,
72+
spark_type = spark_type,
73+
application_file=main_application_file,
74+
main_class=main_class,
75+
executor_path=_sys.executable,
76+
).to_flyte_idl()
77+
78+
super(SdkGenericSparkTask, self).__init__(
79+
task_type,
80+
_task_models.TaskMetadata(
81+
discoverable,
82+
_task_models.RuntimeMetadata(
83+
_task_models.RuntimeMetadata.RuntimeType.FLYTE_SDK,
84+
__version__,
85+
'spark'
86+
),
87+
timeout,
88+
_literal_models.RetryStrategy(retries),
89+
interruptible,
90+
discovery_version,
91+
deprecated
92+
),
93+
_interface.TypedInterface({}, {}),
94+
_MessageToDict(spark_job),
95+
)
96+
97+
# Add Inputs
98+
if task_inputs is not None:
99+
task_inputs(self)
100+
101+
# Container after the Inputs have been updated.
102+
self._container = self._get_container_definition(
103+
environment=environment
104+
)
105+
106+
def _validate_inputs(self, inputs):
107+
"""
108+
:param dict[Text, flytekit.models.interface.Variable] inputs: Input variables to validate
109+
:raises: flytekit.common.exceptions.user.FlyteValidationException
110+
"""
111+
for k, v in _six.iteritems(inputs):
112+
sdk_type =_helpers.get_sdk_type_from_literal_type(v.type)
113+
if sdk_type not in input_types_supported:
114+
raise _user_exceptions.FlyteValidationException(
115+
"Input Type '{}' not supported. Only Primitives are supported for Scala/Java Spark.".format(sdk_type)
116+
)
117+
super(SdkGenericSparkTask, self)._validate_inputs(inputs)
118+
119+
@_exception_scopes.system_entry_point
120+
def add_inputs(self, inputs):
121+
"""
122+
Adds the inputs to this task. This can be called multiple times, but it will fail if an input with a given
123+
name is added more than once, a name collides with an output, or if the name doesn't exist as an arg name in
124+
the wrapped function.
125+
:param dict[Text, flytekit.models.interface.Variable] inputs: names and variables
126+
"""
127+
self._validate_inputs(inputs)
128+
self.interface.inputs.update(inputs)
129+
130+
def _get_container_definition(
131+
self,
132+
environment=None,
133+
):
134+
"""
135+
:rtype: Container
136+
"""
137+
138+
args = []
139+
for k, v in _six.iteritems(self.interface.inputs):
140+
args.append("--{}".format(k))
141+
args.append("{{{{.Inputs.{}}}}}".format(k))
142+
143+
return _task_models.Container(
144+
image=_internal_config.IMAGE.get(),
145+
command=[],
146+
args=args,
147+
resources=_task_models.Resources([], []),
148+
env=environment,
149+
config={}
150+
)

flytekit/common/tasks/spark_task.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ def __init__(
6161
deprecated,
6262
discoverable,
6363
timeout,
64+
spark_type,
6465
spark_conf,
6566
hadoop_conf,
6667
environment,
@@ -88,6 +89,8 @@ def __init__(
8889
hadoop_conf=hadoop_conf,
8990
application_file="local://" + spark_exec_path,
9091
executor_path=_sys.executable,
92+
main_class="",
93+
spark_type=spark_type,
9194
).to_flyte_idl()
9295
super(SdkSparkTask, self).__init__(
9396
task_function,

flytekit/models/task.py

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@
88
from flyteidl.plugins import spark_pb2 as _spark_task
99
from flytekit.plugins import flyteidl as _lazy_flyteidl
1010
from google.protobuf import json_format as _json_format, struct_pb2 as _struct
11-
11+
from flytekit.sdk.spark_types import SparkType as _spark_type
1212
from flytekit.models import common as _common, literals as _literals, interface as _interface
1313
from flytekit.models.core import identifier as _identifier
14+
from flytekit.common.exceptions import user as _user_exceptions
1415

1516

1617
class Resources(_common.FlyteIdlEntity):
@@ -544,7 +545,7 @@ def from_flyte_idl(cls, pb2_object):
544545

545546
class SparkJob(_common.FlyteIdlEntity):
546547

547-
def __init__(self, application_file, spark_conf, hadoop_conf, executor_path):
548+
def __init__(self, spark_type, application_file, main_class, spark_conf, hadoop_conf, executor_path):
548549
"""
549550
This defines a SparkJob target. It will execute the appropriate SparkJob.
550551
@@ -553,10 +554,28 @@ def __init__(self, application_file, spark_conf, hadoop_conf, executor_path):
553554
:param dict[Text, Text] hadoop_conf: A definition of key-value pairs for hadoop config for the job.
554555
"""
555556
self._application_file = application_file
557+
self._spark_type = spark_type
558+
self._main_class = main_class
556559
self._executor_path = executor_path
557560
self._spark_conf = spark_conf
558561
self._hadoop_conf = hadoop_conf
559562

563+
@property
564+
def main_class(self):
565+
"""
566+
The main class to execute
567+
:rtype: Text
568+
"""
569+
return self._main_class
570+
571+
@property
572+
def spark_type(self):
573+
"""
574+
Spark Job Type
575+
:rtype: Text
576+
"""
577+
return self._spark_type
578+
560579
@property
561580
def application_file(self):
562581
"""
@@ -593,8 +612,22 @@ def to_flyte_idl(self):
593612
"""
594613
:rtype: flyteidl.plugins.spark_pb2.SparkJob
595614
"""
615+
616+
if self.spark_type == _spark_type.PYTHON:
617+
application_type = _spark_task.SparkApplication.PYTHON
618+
elif self.spark_type == _spark_type.JAVA:
619+
application_type = _spark_task.SparkApplication.JAVA
620+
elif self.spark_type == _spark_type.SCALA:
621+
application_type = _spark_task.SparkApplication.SCALA
622+
elif self.spark_type == _spark_type.R:
623+
application_type = _spark_task.SparkApplication.R
624+
else:
625+
raise _user_exceptions.FlyteValidationException("Invalid Spark Application Type Specified")
626+
596627
return _spark_task.SparkJob(
628+
applicationType=application_type,
597629
mainApplicationFile=self.application_file,
630+
mainClass=self.main_class,
598631
executorPath=self.executor_path,
599632
sparkConf=self.spark_conf,
600633
hadoopConf=self.hadoop_conf,
@@ -606,9 +639,20 @@ def from_flyte_idl(cls, pb2_object):
606639
:param flyteidl.plugins.spark_pb2.SparkJob pb2_object:
607640
:rtype: SparkJob
608641
"""
642+
643+
application_type = _spark_type.PYTHON
644+
if pb2_object.type == _spark_task.SparkApplication.JAVA:
645+
application_type = _spark_type.JAVA
646+
elif pb2_object.type == _spark_task.SparkApplication.SCALA:
647+
application_type = _spark_type.SCALA
648+
elif pb2_object.type == _spark_task.SparkApplication.R:
649+
application_type = _spark_type.R
650+
609651
return cls(
652+
type= application_type,
610653
spark_conf=pb2_object.sparkConf,
611654
application_file=pb2_object.mainApplicationFile,
655+
main_class=pb2_object.mainClass,
612656
hadoop_conf=pb2_object.hadoopConf,
613657
executor_path=pb2_object.executorPath,
614658
)

flytekit/sdk/spark_types.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import enum
2+
3+
4+
class SparkType(enum.Enum):
5+
PYTHON = 1
6+
SCALA = 2
7+
JAVA = 3
8+
R = 4

flytekit/sdk/tasks.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
from flytekit.common import constants as _common_constants
77
from flytekit.common.exceptions import user as _user_exceptions
88
from flytekit.common.tasks import sdk_runnable as _sdk_runnable_tasks, sdk_dynamic as _sdk_dynamic, \
9-
spark_task as _sdk_spark_tasks, hive_task as _sdk_hive_tasks, sidecar_task as _sdk_sidecar_tasks
9+
spark_task as _sdk_spark_tasks, generic_spark_task as _sdk_generic_spark_task, hive_task as _sdk_hive_tasks, sidecar_task as _sdk_sidecar_tasks
1010
from flytekit.common.tasks import task as _task
1111
from flytekit.common.types import helpers as _type_helpers
12+
from flytekit.sdk.spark_types import SparkType as _spark_type
1213
from flytekit.models import interface as _interface_model
1314

1415

@@ -474,6 +475,7 @@ def wrapper(fn):
474475
discovery_version=cache_version,
475476
retries=retries,
476477
interruptible=interruptible,
478+
spark_type= _spark_type.PYTHON,
477479
deprecated=deprecated,
478480
discoverable=cache,
479481
timeout=timeout or _datetime.timedelta(seconds=0),
@@ -488,6 +490,45 @@ def wrapper(fn):
488490
return wrapper
489491

490492

493+
def generic_spark_task(
494+
spark_type,
495+
main_class,
496+
main_application_file,
497+
cache_version='',
498+
retries=0,
499+
interruptible=None,
500+
inputs=None,
501+
deprecated='',
502+
cache=False,
503+
timeout=None,
504+
spark_conf=None,
505+
hadoop_conf=None,
506+
environment=None,
507+
):
508+
"""
509+
Create a generic spark task. This task will connect to a Spark cluster, configure the environment,
510+
and then execute the mainClass code as the Spark driver program.
511+
512+
"""
513+
514+
return _sdk_generic_spark_task.SdkGenericSparkTask(
515+
task_type=_common_constants.SdkTaskType.SPARK_TASK,
516+
discovery_version=cache_version,
517+
retries=retries,
518+
interruptible=interruptible,
519+
deprecated=deprecated,
520+
discoverable=cache,
521+
timeout=timeout or _datetime.timedelta(seconds=0),
522+
spark_type = spark_type,
523+
task_inputs= inputs,
524+
main_class = main_class or "",
525+
main_application_file = main_application_file or "",
526+
spark_conf=spark_conf or {},
527+
hadoop_conf=hadoop_conf or {},
528+
environment=environment or {},
529+
)
530+
531+
491532
def qubole_spark_task(*args, **kwargs):
492533
"""
493534
:rtype: flytekit.common.tasks.sdk_runnable.SdkRunnableTask
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from __future__ import absolute_import
2+
from __future__ import division
3+
from __future__ import print_function
4+
5+
from flytekit.sdk.tasks import generic_spark_task, inputs, python_task
6+
from flytekit.sdk.types import Types
7+
from flytekit.sdk.spark_types import SparkType
8+
from flytekit.sdk.workflow import workflow_class, Input
9+
10+
11+
scala_spark = generic_spark_task(
12+
spark_type=SparkType.SCALA,
13+
inputs=inputs(partitions=Types.Integer),
14+
main_class="org.apache.spark.examples.SparkPi",
15+
main_application_file="local:///opt/spark/examples/jars/spark-examples.jar",
16+
spark_conf={
17+
'spark.driver.memory': "1000M",
18+
'spark.executor.memory': "1000M",
19+
'spark.executor.cores': '1',
20+
'spark.executor.instances': '2',
21+
},
22+
cache_version='1'
23+
)
24+
25+
26+
@inputs(date_triggered=Types.Datetime)
27+
@python_task(cache_version='1')
28+
def print_every_time(workflow_parameters, date_triggered):
29+
print("My input : {}".format(date_triggered))
30+
31+
32+
@workflow_class
33+
class SparkTasksWorkflow(object):
34+
triggered_date = Input(Types.Datetime)
35+
partitions = Input(Types.Integer)
36+
spark_task = scala_spark(partitions=partitions)
37+
print_always = print_every_time(
38+
date_triggered=triggered_date)

0 commit comments

Comments
 (0)