Example #1
0
 def delete(self, sparkapp_name: str):
     service = SparkAppService()
     try:
         sparkapp_info = service.delete_sparkapp(sparkapp_name)
         return {'data': sparkapp_info.to_dict()}, HTTPStatus.OK
     except NotFoundException:
         return {'data': {'name': sparkapp_name}}, HTTPStatus.OK
Example #2
0
 def setUp(self) -> None:
     super().setUp()
     self._upload_path = os.path.join(BASE_DIR, 'test-spark')
     os.makedirs(self._upload_path)
     self._patch_upload_path = patch(
         'fedlearner_webconsole.sparkapp.service.UPLOAD_PATH',
         self._upload_path)
     self._patch_upload_path.start()
     self._sparkapp_service = SparkAppService()
Example #3
0
    def post(self):
        service = SparkAppService()
        data = request.json

        try:
            config = SparkAppConfig.from_dict(data)
            if config.files:
                config.files = base64.b64decode(config.files)
        except ValueError as err:
            raise InvalidArgumentException(details=err)

        res = service.submit_sparkapp(config=config)
        return {'data': res.to_dict()}, HTTPStatus.CREATED
Example #4
0
    def __init__(self, task_id: int) -> None:
        self.task_id = task_id
        self.task_type = None
        self.files_dir = None
        self.files_path = None
        self.main_application = None
        self.command = []
        self.sparkapp_name = None
        self.args = {}
        self.started = False
        self.error_msg = False

        self.spark_service = SparkAppService()
Example #5
0
class SparkAppServiceTest(unittest.TestCase):
    def setUp(self) -> None:
        super().setUp()
        self._upload_path = os.path.join(BASE_DIR, 'test-spark')
        os.makedirs(self._upload_path)
        self._patch_upload_path = patch(
            'fedlearner_webconsole.sparkapp.service.UPLOAD_PATH',
            self._upload_path)
        self._patch_upload_path.start()
        self._sparkapp_service = SparkAppService()

    def tearDown(self) -> None:
        self._patch_upload_path.stop()
        shutil.rmtree(self._upload_path)
        return super().tearDown()

    def _get_tar_file_path(self) -> str:
        return os.path.join(
            BASE_DIR, 'test/fedlearner_webconsole/test_data/sparkapp.tar')

    def test_get_sparkapp_upload_path(self):
        existable, sparkapp_path = self._sparkapp_service._get_sparkapp_upload_path(
            'test')
        self.assertFalse(existable)

        os.makedirs(sparkapp_path)
        existable, _ = self._sparkapp_service._get_sparkapp_upload_path('test')
        self.assertTrue(existable)

    def test_copy_files_to_target_filesystem(self):
        _, sparkapp_path = self._sparkapp_service._get_sparkapp_upload_path(
            'test')
        self._sparkapp_service._clear_and_make_an_empty_dir(sparkapp_path)
        files_path = self._get_tar_file_path()
        with tempfile.TemporaryDirectory() as temp_dir:
            file_name = files_path.rsplit('/', 1)[-1]
            temp_file_path = os.path.join(temp_dir, file_name)
            shutil.copy(files_path, temp_file_path)
            self._sparkapp_service._copy_files_to_target_filesystem(
                source_filesystem_path=temp_file_path,
                target_filesystem_path=sparkapp_path)

            self.assertTrue(
                os.path.exists(os.path.join(sparkapp_path, 'convertor.py')))

    @patch(
        'fedlearner_webconsole.utils.k8s_client.k8s_client.create_sparkapplication'
    )
    def test_submit_sparkapp(self, mock_create_sparkapp: MagicMock):
        mock_create_sparkapp.return_value = {
            'apiVersion': 'sparkoperator.k8s.io/v1beta2',
            'kind': 'SparkApplication',
            'metadata': {
                'creationTimestamp': '2021-05-18T08:59:16Z',
                'generation': 1,
                'name': 'fl-transformer-yaml',
                'namespace': 'fedlearner',
                'resourceVersion': '432649442',
                'selfLink':
                '/apis/sparkoperator.k8s.io/v1beta2/namespaces/fedlearner/sparkapplications/fl-transformer-yaml',
                'uid': '52d66d27-b7b7-11eb-b9df-b8599fdb0aac'
            },
            'spec': {
                'arguments': ['data.csv', 'data_tfrecords/'],
                'driver': {
                    'coreLimit': '4000m',
                    'cores': 1,
                    'labels': {
                        'version': '3.0.0'
                    },
                    'memory': '512m',
                    'serviceAccount': 'spark',
                },
                'dynamicAllocation': {
                    'enabled': False
                },
                'executor': {
                    'cores': 1,
                    'instances': 1,
                    'labels': {
                        'version': '3.0.0'
                    },
                    'memory': '512m',
                },
                'image': 'dockerhub.com',
                'imagePullPolicy': 'Always',
                'mainApplicationFile': 'transformer.py',
                'mode': 'cluster',
                'pythonVersion': '3',
                'restartPolicy': {
                    'type': 'Never'
                },
                'sparkConf': {
                    'spark.shuffle.service.enabled': 'false'
                },
                'sparkVersion': '3.0.0',
                'type': 'Python',
            },
            'status': {
                'applicationState': {
                    'state': 'COMPLETED'
                },
                'driverInfo': {
                    'podName': 'fl-transformer-yaml-driver',
                    'webUIAddress': '11.249.131.12:4040',
                    'webUIPort': 4040,
                    'webUIServiceName': 'fl-transformer-yaml-ui-svc'
                },
                'executionAttempts': 1,
                'executorState': {
                    'fl-transformer-yaml-bdc15979a314310b-exec-1': 'PENDING',
                    'fl-transformer-yaml-bdc15979a314310b-exec-2': 'COMPLETED'
                },
                'lastSubmissionAttemptTime': '2021-05-18T10:31:13Z',
                'sparkApplicationId': 'spark-a380bfd520164d828a334bcb3a6404f9',
                'submissionAttempts': 1,
                'submissionID': '5bc7e2e7-cc0f-420c-8bc7-138b651a1dde',
                'terminationTime': '2021-05-18T10:32:08Z'
            }
        }

        tarball_file_path = os.path.join(
            BASE_DIR, 'test/fedlearner_webconsole/test_data/sparkapp.tar')
        with open(tarball_file_path, 'rb') as f:
            files_bin = f.read()

        inputs = {
            'name': 'fl-transformer-yaml',
            'files': files_bin,
            'image_url': 'dockerhub.com',
            'driver_config': {
                'cores': 1,
                'memory': '200m',
                'coreLimit': '4000m',
            },
            'executor_config': {
                'cores': 1,
                'memory': '200m',
                'instances': 5,
            },
            'command': ['data.csv', 'data.rd'],
            'main_application': '${prefix}/convertor.py'
        }
        config = SparkAppConfig.from_dict(inputs)
        resp = self._sparkapp_service.submit_sparkapp(config)

        self.assertTrue(
            os.path.exists(
                os.path.join(self._upload_path, 'sparkapp',
                             'fl-transformer-yaml', 'convertor.py')))
        mock_create_sparkapp.assert_called_once()
        self.assertTrue(resp.namespace, 'fedlearner')

    @patch(
        'fedlearner_webconsole.utils.k8s_client.k8s_client.get_sparkapplication'
    )
    def test_get_sparkapp_info(self, mock_get_sparkapp: MagicMock):
        mock_get_sparkapp.return_value = {
            'apiVersion': 'sparkoperator.k8s.io/v1beta2',
            'kind': 'SparkApplication',
            'metadata': {
                'creationTimestamp': '2021-05-18T08:59:16Z',
                'generation': 1,
                'name': 'fl-transformer-yaml',
                'namespace': 'fedlearner',
                'resourceVersion': '432649442',
                'selfLink':
                '/apis/sparkoperator.k8s.io/v1beta2/namespaces/fedlearner/sparkapplications/fl-transformer-yaml',
                'uid': '52d66d27-b7b7-11eb-b9df-b8599fdb0aac'
            },
            'spec': {
                'arguments': ['data.csv', 'data_tfrecords/'],
                'driver': {
                    'coreLimit': '4000m',
                    'cores': 1,
                    'labels': {
                        'version': '3.0.0'
                    },
                    'memory': '512m',
                    'serviceAccount': 'spark',
                },
                'dynamicAllocation': {
                    'enabled': False
                },
                'executor': {
                    'cores': 1,
                    'instances': 1,
                    'labels': {
                        'version': '3.0.0'
                    },
                    'memory': '512m',
                },
                'image': 'dockerhub.com',
                'imagePullPolicy': 'Always',
                'mainApplicationFile': 'transformer.py',
                'mode': 'cluster',
                'pythonVersion': '3',
                'restartPolicy': {
                    'type': 'Never'
                },
                'sparkConf': {
                    'spark.shuffle.service.enabled': 'false'
                },
                'sparkVersion': '3.0.0',
                'type': 'Python',
            },
            'status': {
                'applicationState': {
                    'state': 'COMPLETED'
                },
                'driverInfo': {
                    'podName': 'fl-transformer-yaml-driver',
                    'webUIAddress': '11.249.131.12:4040',
                    'webUIPort': 4040,
                    'webUIServiceName': 'fl-transformer-yaml-ui-svc'
                },
                'executionAttempts': 1,
                'executorState': {
                    'fl-transformer-yaml-bdc15979a314310b-exec-1': 'PENDING',
                    'fl-transformer-yaml-bdc15979a314310b-exec-2': 'COMPLETED'
                },
                'lastSubmissionAttemptTime': '2021-05-18T10:31:13Z',
                'sparkApplicationId': 'spark-a380bfd520164d828a334bcb3a6404f9',
                'submissionAttempts': 1,
                'submissionID': '5bc7e2e7-cc0f-420c-8bc7-138b651a1dde',
                'terminationTime': '2021-05-18T10:32:08Z'
            }
        }

        resp = self._sparkapp_service.get_sparkapp_info('fl-transformer-yaml')

        mock_get_sparkapp.assert_called_once()
        self.assertTrue(resp.namespace, 'fedlearner')

    @patch(
        'fedlearner_webconsole.sparkapp.service.SparkAppService._get_sparkapp_upload_path'
    )
    @patch('fedlearner_webconsole.utils.file_manager.FileManager.remove')
    @patch(
        'fedlearner_webconsole.utils.k8s_client.k8s_client.delete_sparkapplication'
    )
    def test_delete_sparkapp(self, mock_delete_sparkapp: MagicMock,
                             mock_file_mananger_remove: MagicMock,
                             mock_upload_path: MagicMock):
        mock_delete_sparkapp.return_value = {
            'kind': 'Status',
            'apiVersion': 'v1',
            'metadata': {},
            'status': 'Success',
            'details': {
                'name': 'fl-transformer-yaml',
                'group': 'sparkoperator.k8s.io',
                'kind': 'sparkapplications',
                'uid': '52d66d27-b7b7-11eb-b9df-b8599fdb0aac'
            }
        }
        mock_upload_path.return_value = (True, 'test')
        resp = self._sparkapp_service.delete_sparkapp(
            name='fl-transformer-yaml')
        mock_delete_sparkapp.assert_called_once()
        mock_file_mananger_remove.assert_called_once()
        self.assertTrue(resp.name, 'fl-transformer-yaml')
Example #6
0
 def get(self, sparkapp_name: str):
     service = SparkAppService()
     return {
         'data': service.get_sparkapp_info(sparkapp_name).to_dict()
     }, HTTPStatus.OK
Example #7
0
class DataPipelineRunner(IRunner):
    TYPE_PARAMS_MAPPER = {
        DataPipelineType.ANALYZER: {
            'files_dir': 'fedlearner_webconsole/dataset/sparkapp/pipeline',
            'main_application': 'pipeline/analyzer.py',
        },
        DataPipelineType.CONVERTER: {
            'files_dir': 'fedlearner_webconsole/dataset/sparkapp/pipeline',
            'main_application': 'pipeline/converter.py',
        },
        DataPipelineType.TRANSFORMER: {
            'files_dir': 'fedlearner_webconsole/dataset/sparkapp/pipeline',
            'main_application': 'pipeline/transformer.py',
        }
    }

    SPARKAPP_STATE_TO_RUNNER_STATUS = {
        '': RunnerStatus.RUNNING,
        'SUBMITTED': RunnerStatus.RUNNING,
        'PENDING_RERUN': RunnerStatus.RUNNING,
        'RUNNING': RunnerStatus.RUNNING,
        'COMPLETED': RunnerStatus.DONE,
        'SUCCEEDING': RunnerStatus.DONE,
        'FAILED': RunnerStatus.FAILED,
        'SUBMISSION_FAILED': RunnerStatus.FAILED,
        'INVALIDATING': RunnerStatus.FAILED,
        'FAILING': RunnerStatus.FAILED,
        'UNKNOWN': RunnerStatus.FAILED
    }

    def __init__(self, task_id: int) -> None:
        self.task_id = task_id
        self.task_type = None
        self.files_dir = None
        self.files_path = None
        self.main_application = None
        self.command = []
        self.sparkapp_name = None
        self.args = {}
        self.started = False
        self.error_msg = False

        self.spark_service = SparkAppService()

    def start(self, context: Context):
        try:
            self.started = True
            self.args = deepcopy(context.data.get(str(self.task_id), {}))
            self.task_type = DataPipelineType(self.args.pop('task_type'))
            name = self.args.pop('sparkapp_name')
            job_id = uuid4().hex
            self.sparkapp_name = f'pipe-{self.task_type.value}-{job_id}-{name}'

            params = self.__class__.TYPE_PARAMS_MAPPER[self.task_type]
            self.files_dir = os.path.join(Envs.BASE_DIR, params['files_dir'])
            self.files_path = Envs.SPARKAPP_FILES_PATH
            self.main_application = params['main_application']
            self.command = self.args.pop('input')

            files = None
            if self.files_path is None:
                files_obj = io.BytesIO()
                with tarfile.open(fileobj=files_obj, mode='w') as f:
                    f.add(self.files_dir)
                files = files_obj.getvalue()

            config = {
                'name': self.sparkapp_name,
                'files': files,
                'files_path': self.files_path,
                'image_url': Envs.SPARKAPP_IMAGE_URL,
                'volumes': gen_sparkapp_volumes(Envs.SPARKAPP_VOLUMES),
                'driver_config': {
                    'cores':
                    1,
                    'memory':
                    '4g',
                    'volume_mounts':
                    gen_sparkapp_volume_mounts(Envs.SPARKAPP_VOLUME_MOUNTS),
                },
                'executor_config': {
                    'cores':
                    2,
                    'memory':
                    '4g',
                    'instances':
                    1,
                    'volume_mounts':
                    gen_sparkapp_volume_mounts(Envs.SPARKAPP_VOLUME_MOUNTS),
                },
                'main_application': f'${{prefix}}/{self.main_application}',
                'command': self.command,
            }
            config_dict = SparkAppConfig.from_dict(config)
            resp = self.spark_service.submit_sparkapp(config=config_dict)
            logging.info(
                f'created spark app, name: {name}, '
                f'config: {config_dict.__dict__}, resp: {resp.__dict__}')
        except Exception as e:  # pylint: disable=broad-except
            self.error_msg = f'[composer] failed to run this item, err: {e}, \
            trace: {traceback.format_exc()}'

    def result(self, context: Context) -> Tuple[RunnerStatus, dict]:
        if self.error_msg:
            context.set_data(f'failed_{self.task_id}',
                             {'error': self.error_msg})
            return RunnerStatus.FAILED, {}
        if not self.started:
            return RunnerStatus.RUNNING, {}
        resp = self.spark_service.get_sparkapp_info(self.sparkapp_name)
        logging.info(f'sparkapp resp: {resp.__dict__}')
        if not resp.state:
            return RunnerStatus.RUNNING, {}
        return self.__class__.SPARKAPP_STATE_TO_RUNNER_STATUS.get(
            resp.state, RunnerStatus.FAILED), resp.to_dict()