Exemplo n.º 1
0
def test_launch_webui_with_kube_config_loading_success(mocked_browser_check,
                                                       mocker):
    spf_mock = mocker.patch("util.launcher.K8sProxy")
    kube_config_mock = mocker.patch(
        'util.k8s.k8s_info.config.load_kube_config')
    kube_client_mock = mocker.patch(
        'kubernetes.client.configuration.Configuration')
    wfc_mock = mocker.patch("util.launcher.wait_for_connection")
    browser_mock = mocker.patch("util.launcher.webbrowser.open_new")
    input_mock = mocker.patch("util.launcher.wait_for_ctrl_c")

    if get_current_os() in (OS.WINDOWS, OS.MACOS):
        socat_mock = mocker.patch("util.launcher.socat")

    runner = CliRunner()
    runner.invoke(launch.launch, [APP_NAME])

    assert spf_mock.call_count == 1, "port wasn't forwarded"
    assert kube_config_mock.call_count == 1, "kube config wasn't loaded"
    assert kube_client_mock.call_count == 1, "kubernetes api key wasn't read"
    assert wfc_mock.call_count == 1, "connection wasn't checked"
    assert browser_mock.call_count == 1, "browser wasn't started"
    assert input_mock.call_count == 1, "enter wasn't prompted"

    if get_current_os() in (OS.WINDOWS, OS.MACOS):
        # noinspection PyUnboundLocalVariable
        assert socat_mock.start.call_count == 1, "socat wasn't started"
Exemplo n.º 2
0
def test_launch_webui_unsupported_browser(mocked_k8s_config,
                                          mocked_browser_check, mocker):
    mocked_browser_check.return_value = False

    spf_mock = mocker.patch("util.launcher.K8sProxy")
    spf_mock.return_value = 0
    wfc_mock = mocker.patch("util.launcher.wait_for_connection")
    browser_mock = mocker.patch("util.launcher.webbrowser.open_new")
    input_mock = mocker.patch("util.launcher.wait_for_ctrl_c")

    if get_current_os() in (OS.WINDOWS, OS.MACOS):
        socat_mock = mocker.patch("util.launcher.socat")

    runner = CliRunner()
    result = runner.invoke(launch.launch, [APP_NAME])

    assert spf_mock.call_count == 1, "port wasn't forwarded"
    assert wfc_mock.call_count == 0, "connection was checked"
    assert browser_mock.call_count == 0, "browser was not started"
    assert input_mock.call_count == 0, "enter was prompted"

    if get_current_os() in (OS.WINDOWS, OS.MACOS):
        # noinspection PyUnboundLocalVariable
        assert socat_mock.start.call_count == 0, "socat was started"

    assert result.exit_code == 1
Exemplo n.º 3
0
 def validate_config_path(path: str) -> bool:
     if os.path.isdir(path):
         directory_content = os.listdir(path)
         expected_content = ['draft.exe', 'helm.exe'] if system.get_current_os() == system.OS.WINDOWS \
             else ['draft', 'helm']
         return set(expected_content).issubset(directory_content)
     return False
Exemplo n.º 4
0
 def validate_config_path(path: str) -> bool:
     if os.path.isdir(path):
         directory_content = os.listdir(path)
         expected_content = {'helm.exe'} if system.get_current_os() == system.OS.WINDOWS \
             else {'helm'}
         return expected_content.issubset(directory_content)
     return False
Exemplo n.º 5
0
def set_frames_string():
    fallback_frames = "\\|/-"
    if get_current_os() == OS.WINDOWS:
        return fallback_frames
    try:
        utf_frames = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
        utf_frames.encode(sys.stdout.encoding)
        return utf_frames
    except UnicodeEncodeError:
        return fallback_frames
Exemplo n.º 6
0
def test_extract_pack_name_from_path_lack_of_file():
    pack_name = "test_pack_name"
    if get_current_os() == OS.WINDOWS:
        path_to_check = f"C:\\dist\\config\\packs\\{pack_name}\\charts"
    else:
        path_to_check = f"dist/config/packs/{pack_name}/charts"

    ret_pack_name = template.extract_pack_name_from_path(path_to_check)

    assert not ret_pack_name
Exemplo n.º 7
0
def test_extract_pack_name_from_path_folder_too_short():
    pack_name = "test_pack_name"
    if get_current_os() == OS.WINDOWS:
        path_to_check = f"C:\\{pack_name}\\charts"
    else:
        path_to_check = f"/{pack_name}/charts"

    ret_pack_name = template.extract_pack_name_from_path(path_to_check)

    assert not ret_pack_name
Exemplo n.º 8
0
def test_validate_config_path_for_existing_config_dir_with_valid_data(mocker):
    os_path_mock = mocker.patch('os.path.isdir')
    os_path_mock.return_value = True
    os_listdir_mock = mocker.patch('os.listdir')
    os_listdir_mock.return_value = ['draft.exe', 'helm.exe'] if system.get_current_os() == system.OS.WINDOWS \
        else ['draft', 'helm']

    result = Config.validate_config_path(FAKE_PATH)

    assert result
Exemplo n.º 9
0
def test_extract_pack_name_from_path_success():
    pack_name = "test_pack_name"
    if get_current_os() == OS.WINDOWS:
        path_to_check = f"C:\\dist\\config\\packs\\{pack_name}\\charts\\values.yaml"
    else:
        path_to_check = f"dist/config/packs/{pack_name}/charts/values.yaml"

    ret_pack_name = template.extract_pack_name_from_path(path_to_check)

    assert ret_pack_name == pack_name
Exemplo n.º 10
0
def test_launch_webui_with_browser_success(mocked_k8s_config,
                                           mocked_browser_check, mocker):
    spf_mock = mocker.patch("util.launcher.K8sProxy")
    wfc_mock = mocker.patch("util.launcher.wait_for_connection")
    browser_mock = mocker.patch("util.launcher.webbrowser.open_new")
    input_mock = mocker.patch("util.launcher.wait_for_ctrl_c")

    if get_current_os() in (OS.WINDOWS, OS.MACOS):
        socat_mock = mocker.patch("util.launcher.socat")

    runner = CliRunner()
    runner.invoke(launch.launch, [APP_NAME])

    assert spf_mock.call_count == 1, "port wasn't forwarded"
    assert wfc_mock.call_count == 1, "connection wasn't checked"
    assert browser_mock.call_count == 1, "browser wasn't started"
    assert input_mock.call_count == 1, "enter wasn't prompted"

    if get_current_os() in (OS.WINDOWS, OS.MACOS):
        # noinspection PyUnboundLocalVariable
        assert socat_mock.start.call_count == 1, "socat wasn't started"
Exemplo n.º 11
0
def create_environment(experiment_name: str, file_location: str, folder_location: str) -> str:
    """
    Creates a complete environment for executing a training using draft.

    :param experiment_name: name of an experiment used to create a folder
                            with content of an experiment
    :param file_location: location of a training script
    :param folder_location: location of a folder with additional data
    :return: (experiment_folder)
    experiment_folder - folder with experiment's artifacts
    In case of any problems during creation of an enviornment it throws an
    exception with a description of a problem
    """
    log.debug("Create environment - start")
    message_prefix = Texts.CREATE_ENV_MSG_PREFIX

    # create a folder for experiment's purposes
    run_environment_path = get_run_environment_path(experiment_name)
    folder_path = os.path.join(run_environment_path, FOLDER_DIR_NAME)

    try:
        if not os.path.exists(folder_path):
            os.makedirs(folder_path)
    except Exception:
        log.exception("Create environment - creating experiment folder error.")
        raise SubmitExperimentError(message_prefix.format(reason=Texts.EXP_DIR_CANT_BE_CREATED))

    # create a semaphore saying that experiment is under submission
    Path(os.path.join(run_environment_path, EXP_SUB_SEMAPHORE_FILENAME)).touch()

    # copy training script - it overwrites the file taken from a folder_location
    if file_location:
        try:
            shutil.copy2(file_location, folder_path)
            if get_current_os() == OS.WINDOWS:
                os.chmod(os.path.join(folder_path, os.path.basename(file_location)), 0o666)
        except Exception:
            log.exception("Create environment - copying training script error.")
            raise SubmitExperimentError(message_prefix.format(reason=Texts.TRAINING_SCRIPT_CANT_BE_CREATED))

    # copy folder content
    if folder_location:
        try:
            copy_tree(folder_location, folder_path)
        except Exception:
            log.exception("Create environment - copying training folder error.")
            raise SubmitExperimentError(message_prefix.format(reason=Texts.DIR_CANT_BE_COPIED_ERROR_TEXT))

    log.debug("Create environment - end")

    return run_environment_path
Exemplo n.º 12
0
def check_asserts(prepare_mocks: SubmitExperimentMocks, get_namespace_count=1, get_exp_name_count=1, create_env_count=1,
                  cmd_create_count=1, update_conf_count=1, k8s_proxy_count=1, add_exp_count=1, add_run_count=1,
                  update_run_count=0, submit_one_count=1, del_env_count=0, socat_start_count=1,
                  delete_k8s_object_count=0):
    assert prepare_mocks.get_namespace.call_count == get_namespace_count, "current user namespace was not fetched"
    assert prepare_mocks.gen_exp_name.call_count == get_exp_name_count, "experiment name wasn't created"
    assert prepare_mocks.create_env.call_count == create_env_count, "environment wasn't created"
    assert prepare_mocks.cmd_create.call_count == cmd_create_count, "deployment wasn't created"
    assert prepare_mocks.update_conf.call_count == update_conf_count, "configuration wasn't updated"
    assert prepare_mocks.k8s_proxy.call_count == k8s_proxy_count, "port wasn't forwarded"
    assert prepare_mocks.add_exp.call_count == add_exp_count, "experiment model was not created"
    assert prepare_mocks.add_run.call_count == add_run_count, "run model was not created"
    assert prepare_mocks.update_run.call_count == update_run_count, "run model was not updated"
    assert prepare_mocks.submit_one.call_count == submit_one_count, "training wasn't deployed"
    assert prepare_mocks.del_env.call_count == del_env_count, "environment folder was deleted"
    assert prepare_mocks.delete_k8s_object_mock.call_count == delete_k8s_object_count, "experiment was not deleted"
    if get_current_os() in (OS.WINDOWS, OS.MACOS):
        assert prepare_mocks.socat.start.call_count == socat_start_count, "socat wasn't started"
        if socat_start_count > 0:
            prepare_mocks.socat.start.assert_called_with(FAKE_NODE_PORT)
Exemplo n.º 13
0
    def __init__(self, mocker) -> None:
        self.mocker = mocker
        self.get_namespace = mocker.patch("commands.experiment.common.get_kubectl_current_context_namespace",
                                          side_effect=[EXPERIMENT_NAMESPACE])
        self.gen_exp_name = mocker.patch("commands.experiment.common.generate_exp_name_and_labels",
                                         side_effect=[(EXPERIMENT_NAME, {})])
        self.add_exp = mocker.patch("platform_resources.experiment.Experiment.create")
        self.update_experiment = mocker.patch("platform_resources.experiment.Experiment.update")
        self.add_run = mocker.patch("platform_resources.experiment.Run.create")
        self.update_run = mocker.patch("platform_resources.experiment.Run.update")
        self.cmd_create = mocker.patch("draft.cmd.create", side_effect=[("", 0, "")])
        self.submit_one = mocker.patch("commands.experiment.common.submit_draft_pack")
        self.update_conf = mocker.patch("commands.experiment.common.update_configuration", side_effect=[0])
        self.create_env = mocker.patch("commands.experiment.common.create_environment",
                                       side_effect=[(EXPERIMENT_FOLDER, "")])
        self.check_run_env = mocker.patch("commands.experiment.common.check_run_environment",
                                          side_effect=[(EXPERIMENT_FOLDER, "")])
        self.del_env = mocker.patch("commands.experiment.common.delete_environment")

        self.k8s_proxy = mocker.patch("commands.experiment.common.K8sProxy")
        self.k8s_proxy.return_value.__enter__.return_value.tunnel_port = FAKE_NODE_PORT

        self.k8s_get_node_port = mocker.patch("commands.experiment.common.get_app_service_node_port")
        self.k8s_get_node_port.return_value = FAKE_NODE_PORT

        self.socat = mocker.patch("commands.experiment.common.socat") \
            if get_current_os() in (OS.WINDOWS, OS.MACOS) else None
        self.isdir = mocker.patch("os.path.isdir", return_value=True)
        self.isfile = mocker.patch("os.path.isfile", return_value=True)
        self.touch = mocker.patch("commands.experiment.common.Path.touch")

        self.config_mock = mocker.patch('commands.experiment.common.Config')
        self.config_mock.return_value.config_path = FAKE_CLI_CONFIG_DIR_PATH
        self.delete_k8s_object_mock = mocker.patch('commands.experiment.common.delete_k8s_object')
        self.get_pod_count_mock = mocker.patch('commands.experiment.common.get_pod_count', return_value=1)
        self.remove_files = mocker.patch('os.remove')
Exemplo n.º 14
0
def submit_experiment(template: str, name: str = None, run_kind: RunKinds = RunKinds.TRAINING,
                      script_location: str = None, script_parameters: Tuple[str, ...] = None,
                      pack_params: List[Tuple[str, str]] = None, parameter_range: List[Tuple[str, str]] = None,
                      parameter_set: Tuple[str, ...] = None,
                      script_folder_location: str = None,
                      env_variables: List[str] = None,
                      requirements_file: str = None) -> (List[Run], Dict[str, str], str):

    script_parameters = script_parameters if script_parameters else ()
    parameter_set = parameter_set if parameter_set else ()
    parameter_range = parameter_range if parameter_range else []

    log.debug("Submit experiment - start")
    try:
        namespace = get_kubectl_current_context_namespace()
        global submitted_namespace
        submitted_namespace = namespace
    except Exception:
        message = Texts.GET_NAMESPACE_ERROR_MSG
        log.exception(message)
        raise SubmitExperimentError(message)

    try:
        with spinner(text=Texts.PREPARING_RESOURCE_DEFINITIONS_MSG):
            experiment_name, labels = generate_exp_name_and_labels(script_name=script_location,
                                                                   namespace=namespace, name=name,
                                                                   run_kind=run_kind)
            runs_list = prepare_list_of_runs(experiment_name=experiment_name, parameter_range=parameter_range,
                                             parameter_set=parameter_set, template_name=template)
    except SubmitExperimentError as exe:
        log.exception(str(exe))
        raise exe
    except Exception:
        message = Texts.SUBMIT_PREPARATION_ERROR_MSG
        log.exception(message)
        raise SubmitExperimentError(message)

    global submitted_experiment
    submitted_experiment = experiment_name

    # Ctrl-C handling
    signal.signal(signal.SIGINT, ctrl_c_handler_for_submit)
    signal.signal(signal.SIGTERM, ctrl_c_handler_for_submit)

    try:
        config = Config()

        # start port forwarding
        # noinspection PyBroadException
        with K8sProxy(NAUTAAppNames.DOCKER_REGISTRY, port=config.local_registry_port) as proxy:
            # Save port that was actually used in configuration
            if proxy.tunnel_port != config.local_registry_port:
                config.local_registry_port = proxy.tunnel_port

            experiment_run_folders = []  # List of local directories used by experiment's runs
            try:
                # run socat if on Windows or Mac OS
                if get_current_os() in (OS.WINDOWS, OS.MACOS):
                    # noinspection PyBroadException
                    try:
                        with spinner(text=Texts.CLUSTER_CONNECTION_MSG):
                            socat.start(proxy.tunnel_port)
                    except Exception:
                        error_msg = Texts.LOCAL_DOCKER_TUNNEL_ERROR_MSG
                        log.exception(error_msg)
                        raise SubmitExperimentError(error_msg)

                cluster_registry_port = get_app_service_node_port(nauta_app_name=NAUTAAppNames.DOCKER_REGISTRY)

                # prepare environments for all experiment's runs
                for experiment_run in runs_list:
                    if script_parameters and experiment_run.parameters:
                        current_script_parameters = script_parameters + experiment_run.parameters
                    elif script_parameters:
                        current_script_parameters = script_parameters
                    elif experiment_run.parameters:
                        current_script_parameters = experiment_run.parameters
                    else:
                        current_script_parameters = ""

                    run_folder, script_location, pod_count = \
                        prepare_experiment_environment(experiment_name=experiment_name,
                                                       run_name=experiment_run.name,
                                                       local_script_location=script_location,
                                                       script_folder_location=script_folder_location,  # noqa: E501
                                                       script_parameters=current_script_parameters,
                                                       pack_type=template, pack_params=pack_params,
                                                       local_registry_port=proxy.tunnel_port,
                                                       cluster_registry_port=cluster_registry_port,
                                                       env_variables=env_variables,
                                                       requirements_file=requirements_file)
                    # Set correct pod count
                    if not pod_count or pod_count < 1:
                        raise SubmitExperimentError('Unable to determine pod count: make sure that values.yaml '
                                                    'file in your pack has podCount field with positive integer value.')
                    experiment_run.pod_count = pod_count

                    experiment_run_folders.append(run_folder)
                    script_name = None
                    if script_location is not None:
                        script_name = os.path.basename(script_location)

                    # Prepend script_name parameter to run description only for display purposes.
                    experiment_run.parameters = script_parameters if not experiment_run.parameters \
                        else experiment_run.parameters + script_parameters
                    if experiment_run.parameters and script_name:
                        experiment_run.parameters = (script_name, ) + experiment_run.parameters
                    elif script_name:
                        experiment_run.parameters = (script_name, )
            except SubmitExperimentError as e:
                log.exception(Texts.ENV_CREATION_ERROR_MSG)
                e.message += f' {Texts.ENV_CREATION_ERROR_MSG}'
                raise
            except Exception:
                # any error in this step breaks execution of this command
                message = Texts.ENV_CREATION_ERROR_MSG
                log.exception(message)
                # just in case - remove folders that were created with a success
                for experiment_run_folder in experiment_run_folders:
                    delete_environment(experiment_run_folder)

            # if ps or pr option is used - first ask whether experiment(s) should be submitted
            if parameter_range or parameter_set:
                click.echo(Texts.CONFIRM_SUBMIT_MSG)
                click.echo(tabulate({RUN_NAME: [run.name for run in runs_list],
                                     RUN_PARAMETERS: ["\n".join(run.parameters) if run.parameters
                                                      else "" for run in runs_list]},
                                    headers=[RUN_NAME, RUN_PARAMETERS], tablefmt="orgtbl"))

                if not click.confirm(Texts.CONFIRM_SUBMIT_QUESTION_MSG, default=True):
                    for experiment_run_folder in experiment_run_folders:
                        delete_environment(experiment_run_folder)
                    exit()

            # create Experiment model
            # TODO template_name & template_namespace should be filled after Template implementation
            parameter_range_spec = [f'-pr {param_name} {param_value}' for param_name, param_value in parameter_range]
            parameter_set_spec = [f'-ps {ps_spec}' for ps_spec in parameter_set]
            experiment_parameters_spec = list(script_parameters) + parameter_range_spec + parameter_set_spec
            experiment = experiments_model.Experiment(name=experiment_name, template_name=template,
                                                      parameters_spec=experiment_parameters_spec,
                                                      template_namespace="template-namespace")

            experiment.create(namespace=namespace, labels=labels)

            # submit runs
            run_errors = {}
            for run, run_folder in zip(runs_list, experiment_run_folders):
                try:
                    run.state = RunStatus.QUEUED
                    with spinner(text=Texts.CREATING_RESOURCES_MSG.format(run_name=run.name)):
                        # Add Run object with runKind label and pack params as annotations
                        run.create(namespace=namespace, labels={'runKind': run_kind.value},
                                   annotations={pack_param_name: pack_param_value
                                                for pack_param_name, pack_param_value in pack_params})
                        submitted_runs.append(run)
                        submit_draft_pack(run_folder, namespace=namespace)
                except Exception as exe:
                    delete_environment(run_folder)
                    try:
                        run.state = RunStatus.FAILED
                        run_errors[run.name] = str(exe)
                        run.update()
                    except Exception as rexe:
                        # update of non-existing run may fail
                        log.debug(Texts.ERROR_DURING_PATCHING_RUN.format(str(rexe)))

            # Delete experiment if no Runs were submitted
            if not submitted_runs:
                click.echo(Texts.SUBMISSION_FAIL_ERROR_MSG)
                delete_k8s_object("experiment", experiment_name)

            # Change experiment status to submitted
            experiment.state = experiments_model.ExperimentStatus.SUBMITTED
            experiment.update()
    except LocalPortOccupiedError as exe:
        click.echo(exe.message)
        raise SubmitExperimentError(exe.message)
    except K8sProxyCloseError:
        log.exception('Error during closing of a proxy for a {}'.format(NAUTAAppNames.DOCKER_REGISTRY))
        raise K8sProxyCloseError(Texts.PROXY_CLOSE_ERROR_MSG)
    except K8sProxyOpenError:
        error_msg = Texts.PROXY_OPEN_ERROR_MSG
        log.exception(error_msg)
        raise SubmitExperimentError(error_msg)
    except SubmitExperimentError:
        raise
    except Exception as exe:
        error_msg = Texts.SUBMIT_OTHER_ERROR_MSG
        log.exception(error_msg)
        raise SubmitExperimentError(error_msg) from exe
    finally:
        with spinner(text=Texts.CLUSTER_CONNECTION_CLOSING_MSG):
            # noinspection PyBroadException
            try:
                socat.stop()
            except Exception:
                log.exception("Error during closing of a proxy for a local docker-host tunnel")
                raise K8sProxyCloseError(Texts.DOCKER_TUNNEL_CLOSE_ERROR_MSG)
        # remove semaphores from all exp folders
        remove_sempahore(experiment_name)

    log.debug("Submit - finish")
    return runs_list, run_errors, script_location
Exemplo n.º 15
0
def create_environment(experiment_name: str,
                       file_location: str = None,
                       folder_location: str = None,
                       show_folder_size_warning=True,
                       max_folder_size_in_bytes=1024 * 1024,
                       spinner_to_hide=None) -> str:
    """
    Creates a complete environment for executing a training using draft.

    :param experiment_name: name of an experiment used to create a folder
                            with content of an experiment
    :param file_location: location of a training script
    :param folder_location: location of a folder with additional data
    :param show_folder_size_warning: if True, a warning will be shown if script folder size exceeds
     value in max_folder_size_in_bytes param
    :param max_folder_size_in_bytes: maximum script folder size,
    :param spinner_to_hide: provide spinner, if it should be hidden before folder size warning
    :return: (experiment_folder)
    experiment_folder - folder with experiment's artifacts
    In case of any problems during creation of an enviornment it throws an
    exception with a description of a problem
    """
    log.debug("Create environment - start")
    message_prefix = Texts.CREATE_ENV_MSG_PREFIX

    # create a folder for experiment's purposes
    run_environment_path = get_run_environment_path(experiment_name)
    folder_path = os.path.join(run_environment_path, FOLDER_DIR_NAME)

    try:
        if not os.path.exists(folder_path):
            os.makedirs(folder_path)
    except Exception:
        log.exception("Create environment - creating experiment folder error.")
        raise SubmitExperimentError(
            message_prefix.format(reason=Texts.EXP_DIR_CANT_BE_CREATED))

    # create a semaphore saying that experiment is under submission
    Path(os.path.join(run_environment_path,
                      EXP_SUB_SEMAPHORE_FILENAME)).touch()

    # copy training script - it overwrites the file taken from a folder_location
    if file_location:
        try:
            shutil.copy2(file_location, folder_path)
            if get_current_os() == OS.WINDOWS:
                os.chmod(
                    os.path.join(folder_path, os.path.basename(file_location)),
                    0o666)  # nosec
        except Exception:
            log.exception(
                "Create environment - copying training script error.")
            raise SubmitExperimentError(
                message_prefix.format(
                    reason=Texts.TRAINING_SCRIPT_CANT_BE_CREATED))

    # copy folder content
    if folder_location:
        folder_size = get_total_directory_size_in_bytes(folder_location)
        if show_folder_size_warning and folder_size >= max_folder_size_in_bytes:
            if spinner_to_hide:
                spinner_to_hide.hide()
            if not click.confirm(
                    f'Experiment\'s script folder location size ({folder_size/1024/1024:.2f} MB) '
                    f'exceeds {max_folder_size_in_bytes/1024/1024:.2f} MB. '
                    f'It is highly recommended to use input/output shares for large amounts of data '
                    f'instead of submitting them along with experiment. Do you want to continue?'
            ):
                exit(2)
            if spinner_to_hide:
                spinner_to_hide.show()
        try:
            copy_tree(folder_location, folder_path)
        except Exception:
            log.exception(
                "Create environment - copying training folder error.")
            raise SubmitExperimentError(
                message_prefix.format(
                    reason=Texts.DIR_CANT_BE_COPIED_ERROR_TEXT))

    log.debug("Create environment - end")

    return run_environment_path
Exemplo n.º 16
0
def launch_app(k8s_app_name: NAUTAAppNames = None,
               no_launch: bool = False,
               port: int = None,
               app_name: str = None,
               number_of_retries: int = 0,
               url_end: str = "",
               namespace: str = None):
    try:
        with spinner(text=Texts.LAUNCHING_APP_MSG) as proxy_spinner, \
             K8sProxy(nauta_app_name=k8s_app_name, port=port, app_name=app_name,
                      number_of_retries=number_of_retries, namespace=namespace) as proxy:
            url = FORWARDED_URL.format(proxy.tunnel_port, url_end)

            # run socat if on Windows or Mac OS
            if get_current_os() in (OS.WINDOWS, OS.MACOS):
                # noinspection PyBroadException
                try:
                    socat.start(proxy.container_port)
                except Exception:
                    err_message = Texts.LOCAL_DOCKER_TUNNEL_ERROR_MSG
                    logger.exception(err_message)
                    raise LaunchError(err_message)

            if k8s_app_name == NAUTAAppNames.INGRESS:
                config.load_kube_config()
                user_token = configuration.Configuration().api_key.get(
                    'authorization')
                prepared_user_token = user_token.replace('Bearer ', '')
                url = f'{url}?token={prepared_user_token}'

            if not no_launch:

                if is_gui_browser_available():
                    wait_for_connection(url)
                    webbrowser.open_new(url)
                    proxy_spinner.hide()
                else:
                    click.echo(Texts.NO_WEB_BROWSER_ERROR_MSG)

            if port and port != proxy.tunnel_port:
                click.echo(
                    Texts.CANNOT_USE_PORT.format(
                        required_port=port, random_port=proxy.tunnel_port))

            proxy_spinner.hide()
            click.echo(Texts.GO_TO_MSG.format(url=url))
            click.echo(Texts.PROXY_CREATED_MSG)
            wait_for_ctrl_c()
    except K8sProxyCloseError:
        err_message = Texts.PROXY_CLOSE_ERROR_MSG.format(app_name=k8s_app_name)
        raise ProxyClosingError(err_message)
    except LocalPortOccupiedError as exe:
        err_message = Texts.PROXY_CREATED_EXTENDED_ERROR_MSG.format(
            app_name=k8s_app_name, reason=exe.message)
        raise LaunchError(err_message)
    except K8sProxyOpenError:
        error_msg = Texts.PROXY_CREATED_ERROR_MSG.format(app_name=k8s_app_name)
        logger.exception(error_msg)
        raise LaunchError(error_msg)
    except LaunchError as e:
        raise e
    except Exception:
        err_message = Texts.WEB_APP_LAUCH_FAIL_MSG
        logger.exception(err_message)
        raise LaunchError(err_message)
    finally:
        # noinspection PyBroadException
        # terminate socat if on Windows or Mac OS
        if get_current_os() in (OS.WINDOWS, OS.MACOS):
            # noinspection PyBroadException
            try:
                with spinner(text=Texts.WEB_APP_CLOSING_MSG):
                    socat.stop()
            except Exception:
                err_message = Texts.PROXY_CLOSE_ERROR_MSG.format(k8s_app_name)
                raise ProxyClosingError(err_message)
Exemplo n.º 17
0
def start_port_forwarding(
        k8s_app_name: NAUTAAppNames,
        port: int = None,
        app_name: str = None,
        number_of_retries: int = 0,
        namespace: str = None) -> (subprocess.Popen, Optional[int], int):
    """
    Creates a proxy responsible for forwarding requests to and from a
    kubernetes' local docker proxy. In case of any errors during creating the
    process - throws a RuntimeError exception with a short description of
    a cause of a problem.
    When proxy created by this function is no longer needed - it should
    be closed by calling kill() function on a process returned by this
    function.

    :param k8s_app_name: name of kubernetes application for tunnel creation
                         value taken from NAUTAAppNames enum
    :param port: if given - the system will try to use it as a local port. Random port will be used
     if that port is not available
    :return:
        instance of a process with proxy, tunneled port and container port
    """
    logger.debug("Start port forwarding")

    try:
        service_node_port = None
        service_container_port = None

        app_services = get_app_services(nauta_app_name=k8s_app_name,
                                        namespace=namespace,
                                        app_name=app_name)

        if app_services:
            service_node_port = app_services[0].spec.ports[0].node_port
            if service_node_port:
                logger.debug('Service node port pod has been found: {}'.format(
                    service_node_port))

            service_container_port = app_services[0].spec.ports[0].port
            if service_container_port:
                logger.debug(
                    'Service container port has been found: {}'.format(
                        service_container_port))
            service_name = app_services[0].metadata.name
            namespace = app_services[0].metadata.namespace

        if not service_node_port and not service_container_port:
            logger.error(f'Cannot find open ports for {k8s_app_name} app')
            raise KubernetesError(Texts.PROXY_CREATION_MISSING_PORT_ERROR_MSG)

        if port:
            if check_port_availability(port):
                tunnel_port = port
            else:
                tunnel_port = find_random_available_port()
        else:
            tunnel_port = find_random_available_port()

        if system.get_current_os() == system.OS.WINDOWS:
            port_forward_command = [
                'FOR', '/L', '%N', 'IN', '()', 'DO', 'kubectl', 'port-forward',
                f'--namespace={namespace}', f'service/{service_name}',
                f'{tunnel_port}:{service_container_port}'
            ]
        else:
            port_forward_command = [
                'while', 'true;', 'do', 'kubectl', 'port-forward',
                f'--namespace={namespace} ', f'service/{service_name}',
                f'{tunnel_port}:{service_container_port};', 'done'
            ]

        logger.debug(port_forward_command)

        process = None
        if number_of_retries:
            for i in range(number_of_retries - 1):
                try:
                    process = system.execute_subprocess_command(
                        port_forward_command, shell=True, join=True)
                except Exception:
                    logger.exception(
                        "Error during setting up proxy - retrying.")
                else:
                    break
                time.sleep(5)

        if not process:
            process = system.execute_subprocess_command(port_forward_command,
                                                        shell=True,
                                                        join=True)

    except KubernetesError as exe:
        raise RuntimeError(exe)
    except LocalPortOccupiedError as exe:
        raise exe
    except Exception:
        raise RuntimeError(Texts.PROXY_CREATION_OTHER_ERROR_MSG)

    logger.info("Port forwarding - proxy set up")
    return process, tunnel_port, service_container_port