<commit_before>#   Foremast - Pipeline Tooling
#
#   Copyright 2016 Gogo, LLC #
#   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.

"""POST a new task or check status of running task"""
from functools import partial
import json
import logging

import requests
from tryagain import call as retry_call

from ..consts import API_URL, GATE_CA_BUNDLE, GATE_CLIENT_CERT, HEADERS, DEFAULT_TASK_TIMEOUT, TASK_TIMEOUTS
from ..exceptions import SpinnakerTaskError

LOG = logging.getLogger(__name__)

def post_task(task_data):
    """POST JSON to Spinnaker /tasks.

    Args:
        task_data (str): the task json that needs posted.

    Returns:
        str: taskid.
    """

    url = '{}/tasks'.format(API_URL)

    if isinstance(task_data, str):
        task_json = task_data
    else:
        task_json = json.dumps(task_data)

    resp = requests.post(url,
                         data=task_json,
                         headers=HEADERS,
                         verify=GATE_CA_BUNDLE,
                         cert=GATE_CLIENT_CERT)
    resp_json = resp.json()

    LOG.debug(resp_json)

    assert resp.ok, 'Spinnaker communication error: {0}'.format(
        resp.text)

    return resp_json['ref']

def _check_task(taskid):
    """Check task status.

    Args:
        taskid (str): the task id returned from create_elb.

    Returns:
        polls for task status.
    """
    try:
        taskurl = taskid.get('ref', '0000')
    except AttributeError:
        taskurl = taskid

    taskid = taskurl.split('/tasks/')[-1]

    LOG.info('Checking taskid %s', taskid)

    url = '{}/tasks/{}'.format(API_URL, taskid)
    task_response = requests.get(url,
                                 headers=HEADERS,
                                 verify=GATE_CA_BUNDLE,
                                 cert=GATE_CLIENT_CERT)

    LOG.debug(task_response.json())

    assert task_response.ok, 'Spinnaker communication error: {0}'.format(
        task_response.text)

    task_state = task_response.json()
    status = task_state['status']
    LOG.info('Current task status: %s', status)

    if status == 'SUCCEEDED':
        return status
    elif status == 'TERMINAL':
        raise SpinnakerTaskError(task_state)
    else:
        raise ValueError

def check_task(taskid, timeout=DEFAULT_TASK_TIMEOUT, wait=2):
    """wrapper for check_task

    Args:
        taskid (str): the task id returned from post_task
        timeout (int) (optional): how long to wait before failing the task
        wait (int, optional): Seconds to pause between polling attempts.

    Returns:
        polls for task status.
    """
    max_attempts = int(timeout / wait)
    return retry_call(partial(_check_task, taskid),
                      max_attempts=max_attempts,
                      wait=wait,
                      exceptions=(AssertionError, ValueError))

def wait_for_task(task_data):
    """Run task and check the result

    Args:
        task_data (str): the task json to execute

    Returns:
        polls for task status
    """
    taskid = post_task(task_data)

    if isinstance(task_data, str):
        json_data = json.loads(task_data)
    else:
        json_data = task_data

    # inspect the task to see if a timeout is configured
    job = json_data['job'][0]
    env = job.get('credentials')
    task_type = job.get('type')

    timeout = TASK_TIMEOUTS.get(env, dict()).get(task_type, DEFAULT_TASK_TIMEOUT)

    LOG.debug("Task %s will timeout after %s", task_type, timeout)

    check_task(taskid, timeout)
<commit_msg>fix: Use custom exception for inconclusive Task<commit_after>#   Foremast - Pipeline Tooling
#
#   Copyright 2016 Gogo, LLC #
#   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.

"""POST a new task or check status of running task"""
from functools import partial
import json
import logging

import requests
from tryagain import call as retry_call

from ..consts import API_URL, GATE_CA_BUNDLE, GATE_CLIENT_CERT, HEADERS, DEFAULT_TASK_TIMEOUT, TASK_TIMEOUTS
from ..exceptions import SpinnakerTaskError, SpinnakerTaskInconclusiveError

LOG = logging.getLogger(__name__)

def post_task(task_data):
    """POST JSON to Spinnaker /tasks.

    Args:
        task_data (str): the task json that needs posted.

    Returns:
        str: taskid.
    """

    url = '{}/tasks'.format(API_URL)

    if isinstance(task_data, str):
        task_json = task_data
    else:
        task_json = json.dumps(task_data)

    resp = requests.post(url,
                         data=task_json,
                         headers=HEADERS,
                         verify=GATE_CA_BUNDLE,
                         cert=GATE_CLIENT_CERT)
    resp_json = resp.json()

    LOG.debug(resp_json)

    assert resp.ok, 'Spinnaker communication error: {0}'.format(
        resp.text)

    return resp_json['ref']

def _check_task(taskid):
    """Check task status.

    Args:
        taskid (str): the task id returned from create_elb.

    Returns:
        polls for task status.
    """
    try:
        taskurl = taskid.get('ref', '0000')
    except AttributeError:
        taskurl = taskid

    taskid = taskurl.split('/tasks/')[-1]

    LOG.info('Checking taskid %s', taskid)

    url = '{}/tasks/{}'.format(API_URL, taskid)
    task_response = requests.get(url,
                                 headers=HEADERS,
                                 verify=GATE_CA_BUNDLE,
                                 cert=GATE_CLIENT_CERT)

    LOG.debug(task_response.json())

    assert task_response.ok, 'Spinnaker communication error: {0}'.format(
        task_response.text)

    task_state = task_response.json()
    status = task_state['status']
    LOG.info('Current task status: %s', status)

    if status == 'SUCCEEDED':
        return status
    elif status == 'TERMINAL':
        raise SpinnakerTaskError(task_state)
    else:
        raise ValueError

def check_task(taskid, timeout=DEFAULT_TASK_TIMEOUT, wait=2):
    """wrapper for check_task

    Args:
        taskid (str): the task id returned from post_task
        timeout (int) (optional): how long to wait before failing the task
        wait (int, optional): Seconds to pause between polling attempts.

    Returns:
        polls for task status.

    Raises:
        AssertionError: API did not responde with a 200 status code.
        foremast.exceptions.SpinnakerTaskInconclusiveError: Task did not reach a
            terminal state in the given time.

    """
    max_attempts = int(timeout / wait)
    try:
        return retry_call(
            partial(_check_task, taskid),
            max_attempts=max_attempts,
            wait=wait,
            exceptions=(AssertionError, ValueError), )
    except ValueError:
        raise SpinnakerTaskInconclusiveError('Task failed to complete in time: {0}'.format(taskid))

def wait_for_task(task_data):
    """Run task and check the result

    Args:
        task_data (str): the task json to execute

    Returns:
        polls for task status
    """
    taskid = post_task(task_data)

    if isinstance(task_data, str):
        json_data = json.loads(task_data)
    else:
        json_data = task_data

    # inspect the task to see if a timeout is configured
    job = json_data['job'][0]
    env = job.get('credentials')
    task_type = job.get('type')

    timeout = TASK_TIMEOUTS.get(env, dict()).get(task_type, DEFAULT_TASK_TIMEOUT)

    LOG.debug("Task %s will timeout after %s", task_type, timeout)

    check_task(taskid, timeout)
<|endoftext|>


<commit_before>from contextlib import contextmanager
from itertools import chain
from mock import Mock
import os

import django
from django.apps import apps
from django.db import connections
from django.db.utils import ConnectionHandler, NotSupportedError
from django.conf import settings

if not apps.ready:
    # Do the Django set up when running as a stand-alone unit test.
    # That's why this module has to be imported before any Django models.
    if 'DJANGO_SETTINGS_MODULE' not in os.environ:
        os.environ['DJANGO_SETTINGS_MODULE'] = 'kive.settings'
    settings.LOGGING['handlers']['console']['level'] = 'CRITICAL'
    django.setup()

    # Disable database access, these are pure unit tests.
    db = connections.databases['default']
    db['PASSWORD'] = '****'
    db['USER'] = '**Database disabled for unit tests**'
    ConnectionHandler.__getitem__ = Mock(name='mock_connection')
    mock_ops = ConnectionHandler.__getitem__.return_value.ops  # @UndefinedVariable
    mock_execute = mock_ops.compiler.return_value.return_value.execute_sql
    mock_execute.side_effect = NotSupportedError("Mock database can't execute sql.")

@contextmanager
def mock_relations(*models):
    """ Mock all related field managers to make pure unit tests possible.

    with mock_relations(Dataset):
        dataset = Dataset()
        check = dataset.content_checks.create()  # returns mock object
    """
    from django_mock_queries.query import MockSet  # fails if imported before setup
    try:
        for model in models:
            model_name = model._meta.object_name
            model.old_relations = {}
            model.old_objects = model.objects
            model.old_save = model.save
            for related_object in chain(model._meta.related_objects,
                                        model._meta.many_to_many):
                name = related_object.name
                old_relation = getattr(model, name)
                model.old_relations[name] = old_relation
                if related_object.one_to_one:
                    new_relation = Mock(name='{}.{}'.format(model_name, name))
                else:
                    new_relation = MockSet(cls=old_relation.field.model)
                setattr(model, name, new_relation)
            model.objects = Mock(name=model_name + '.objects')
            model.save = Mock(name=model_name + '.save')

        yield

    finally:
        for model in models:
            old_save = getattr(model, 'old_save', None)
            if old_save is not None:
                model.save = old_save
                del model.old_save
            old_objects = getattr(model, 'old_objects', None)
            if old_objects is not None:
                model.objects = old_objects
                del model.old_objects
            old_relations = getattr(model, 'old_relations', None)
            if old_relations is not None:
                for name, relation in old_relations.iteritems():
                    setattr(model, name, relation)
                del model.old_relations

def mocked_relations(target, *models):
    """ A decorator version of mock_relations.

    This can decorate a method or a class. Decorating a class is equivalent to
    decorating all the methods whose names start with "test_".
    """
    if isinstance(target, type):
        for attr in dir(target):
            if attr.startswith('test_'):
                original_method = getattr(target, attr)
                setattr(target, attr, mocked_relations(original_method, *models))
        return target

    def wrapped(*args, **kwargs):
        with mock_relations(*models):
            return target(*args, **kwargs)
    return wrapped
<commit_msg>Fix bug in mocked_relations.<commit_after>from contextlib import contextmanager
from itertools import chain
from mock import Mock
import os

import django
from django.apps import apps
from django.db import connections
from django.db.utils import ConnectionHandler, NotSupportedError
from django.conf import settings

if not apps.ready:
    # Do the Django set up when running as a stand-alone unit test.
    # That's why this module has to be imported before any Django models.
    if 'DJANGO_SETTINGS_MODULE' not in os.environ:
        os.environ['DJANGO_SETTINGS_MODULE'] = 'kive.settings'
    settings.LOGGING['handlers']['console']['level'] = 'CRITICAL'
    django.setup()

    # Disable database access, these are pure unit tests.
    db = connections.databases['default']
    db['PASSWORD'] = '****'
    db['USER'] = '**Database disabled for unit tests**'
    ConnectionHandler.__getitem__ = Mock(name='mock_connection')
    mock_ops = ConnectionHandler.__getitem__.return_value.ops  # @UndefinedVariable
    mock_execute = mock_ops.compiler.return_value.return_value.execute_sql
    mock_execute.side_effect = NotSupportedError("Mock database can't execute sql.")

@contextmanager
def mock_relations(*models):
    """ Mock all related field managers to make pure unit tests possible.

    with mock_relations(Dataset):
        dataset = Dataset()
        check = dataset.content_checks.create()  # returns mock object
    """
    from django_mock_queries.query import MockSet  # fails if imported before setup
    try:
        for model in models:
            model_name = model._meta.object_name
            model.old_relations = {}
            model.old_objects = model.objects
            model.old_save = model.save
            for related_object in chain(model._meta.related_objects,
                                        model._meta.many_to_many):
                name = related_object.name
                old_relation = getattr(model, name)
                model.old_relations[name] = old_relation
                if related_object.one_to_one:
                    new_relation = Mock(name='{}.{}'.format(model_name, name))
                else:
                    new_relation = MockSet(cls=old_relation.field.model)
                setattr(model, name, new_relation)
            model.objects = Mock(name=model_name + '.objects')
            model.save = Mock(name=model_name + '.save')

        yield

    finally:
        for model in models:
            old_save = getattr(model, 'old_save', None)
            if old_save is not None:
                model.save = old_save
                del model.old_save
            old_objects = getattr(model, 'old_objects', None)
            if old_objects is not None:
                model.objects = old_objects
                del model.old_objects
            old_relations = getattr(model, 'old_relations', None)
            if old_relations is not None:
                for name, relation in old_relations.iteritems():
                    setattr(model, name, relation)
                del model.old_relations

def mocked_relations(*models):
    """ A decorator version of mock_relations.

    This can decorate a method or a class. Decorating a class is equivalent to
    decorating all the methods whose names start with "test_".
    """
    def decorator(target):
        if isinstance(target, type):
            for attr in dir(target):
                if attr.startswith('test_'):
                    original_method = getattr(target, attr)
                    setattr(target, attr, mocked_relations(original_method, *models))
            return target

        def wrapped(*args, **kwargs):
            with mock_relations(*models):
                return target(*args, **kwargs)
        return wrapped
    return decorator
<|endoftext|>


<commit_before># This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.

import re

import requests
from libmozdata.bugzilla import Bugzilla, BugzillaUser

from auto_nag import logger, utils
from auto_nag.bzcleaner import BzCleaner
from auto_nag.people import People

PUSHLOG_PAT = re.compile(r"Pushlog: (.+)")
BUG_PAT = re.compile(r"[\t ]*[Bb][Uu][Gg][\t ]*([0-9]+)")

def is_ignorable_path(path: str) -> bool:
    # TODO: also ignore other kinds of files that certainly can't cause regressions.

    if any(
        path.endswith(ext) for ext in (".txt", ".md", ".rst", ".pdf", ".doc", ".otf")
    ):
        return True

    # This code was adapted from https://github.com/mozsearch/mozsearch/blob/2e24a308bf66b4c149683bfeb4ceeea3b250009a/router/router.py#L127
    if (
        "/test/" in path
        or "/tests/" in path
        or "/mochitest/" in path
        or "/unit/" in path
        or "/gtest/" in path
        or "testing/" in path
        or "/jsapi-tests/" in path
        or "/reftests/" in path
        or "/reftest/" in path
        or "/crashtests/" in path
        or "/crashtest/" in path
        or "/gtests/" in path
        or "/googletest/" in path
    ):
        return True

    return False

class FuzzingBisectionWithoutRegressedBy(BzCleaner):
    def __init__(self) -> None:
        super().__init__()
        self.people = People.get_instance()
        self.autofix_regressed_by = {}
        self.bzmail_to_nickname = {}

    def description(self):
        return "Bugs with a fuzzing bisection and without regressed_by"

    def handle_bug(self, bug, data):
        bugid = str(bug["id"])
        data[bugid] = {
            "assigned_to_email": bug["assigned_to"],
            "assigned_to_nickname": bug["assigned_to_detail"]["nick"],
        }
        self.bzmail_to_nickname[bug["assigned_to"]] = bug["assigned_to_detail"]["nick"]
        return bug

    def set_autofix(self, bugs):
        for bug_id, bug in bugs.items():
            if "regressor_bug_id" in bug:
                self.autofix_regressed_by[bug_id] = {
                    "comment": {
                        "body": "Setting regressed_by field after analyzing regression range found by bugmon."
                    },
                    "regressed_by": {"add": [bug["regressor_bug_id"]]},
                }
            elif "needinfo_target" in bug:
                self.add_auto_ni(
                    bug_id,
                    bug["needinfo_target"],
                )
            else:
                assert (
                    False
                ), "If we are here, we should either have a regressor or a needinfo target"

    def get_autofix_change(self) -> dict:
        return self.autofix_regressed_by

    def get_bz_params(self, date):
        return {
            "include_fields": ["assigned_to"],
            "f1": "regressed_by",
            "o1": "isempty",
            "n2": 1,
            "f2": "regressed_by",
            "o2": "everchanged",
            "n3": 1,
            "f3": "longdesc",
            "o3": "casesubstring",
            "v3": "since this bug contains a bisection range, could you fill (if possible) the regressed_by field",
            # TODO: Nag in the duplicate target instead, making sure it doesn't already have regressed_by.
            "f4": "resolution",
            "o4": "notequals",
            "v4": "DUPLICATE",
            "emaillongdesc1": "1",
            "emailtype1": "exact",
            "email1": "bugmon@mozilla.com",
        }

    def comment_handler(self, bug, bug_id, bugs):
        range_found = False
        # We start from the last comment just in case bugmon has updated the range.
        for comment in bug["comments"][::-1]:
            if (
                "BugMon: Reduced build range" not in comment["text"]
                and "The bug appears to have been introduced in the following build range"
                not in comment["text"]
            ):
                continue

            range_found = True

            # Try to parse the regression range to find the regressor or at least somebody good to needinfo.
            pushlog_match = PUSHLOG_PAT.search(comment["text"])
            url = (
                pushlog_match.group(1).replace("pushloghtml", "json-pushes")
                + "&full=1&version=2"
            )
            r = requests.get(url)
            r.raise_for_status()

            changesets = [
                changeset
                for push in r.json()["pushes"].values()
                for changeset in push["changesets"]
                if any(not is_ignorable_path(path) for path in changeset["files"])
            ]

            regressor_bug_ids = set()
            for changeset in changesets:
                bug_match = BUG_PAT.search(changeset["desc"])
                if bug_match is not None:
                    regressor_bug_ids.add(bug_match.group(1))

            if len(regressor_bug_ids) == 1:
                # Only one bug in the regression range, we are sure about the regressor!
                bugs[bug_id]["regressor_bug_id"] = regressor_bug_ids.pop()
                break

            if "needinfo_target" not in bugs[bug_id]:
                authors = set(changeset["author"] for changeset in changesets)
                # TODO: also needinfo when there is more than one author, e.g. if there are up to three (where three should be a configurable number).
                if len(authors) == 1:
                    author = authors.pop()
                    author_parts = author.split("<")
                    author_email = author_parts[1][:-1]
                    bzmail = self.people.get_bzmail_from_name(author_email)
                    if bzmail is None:
                        logger.warning(f"No bzmail for {author} in bug {bug_id}")
                        continue
                    self.bzmail_to_nickname[bzmail] = ""
                    bugs[bug_id]["needinfo_target"] = {
                        "mail": bzmail,
                        "nickname": "",
                    }
                    break

        # Exclude bugs that do not have a range found by BugMon.
        if not range_found:
            del bugs[bug_id]

    def find_regressor_or_needinfo_target(self, bugs: dict) -> dict:
        # Needinfo assignee when there is one.
        for bug in bugs.values():
            if not utils.is_no_assignee(bug["assigned_to_email"]):
                bug["needinfo_target"] = {
                    "mail": bug["assigned_to_email"],
                    "nickname": bug["assigned_to_nickname"],
                }

        Bugzilla(
            bugids=self.get_list_bugs(bugs),
            commenthandler=self.comment_handler,
            commentdata=bugs,
            comment_include_fields=["text"],
        ).get_data().wait()

        def user_handler(user: dict) -> None:
            self.bzmail_to_nickname[user["name"]] = user["nick"]

        users = set(
            bzmail
            for bzmail, nickname in self.bzmail_to_nickname.items()
            if not nickname
        )
        if len(users) > 0:
            BugzillaUser(
                user_names=list(users),
                user_handler=user_handler,
                include_fields=["name", "nick"],
            ).wait()

        for bug in bugs.values():
            if "needinfo_target" in bug and not bug["needinfo_target"]["nickname"]:
                bug["needinfo_target"]["nickname"] = self.bzmail_to_nickname[
                    bug["needinfo_target"]["mail"]
                ]

        # Exclude all bugs where we couldn't find a definite regressor bug ID or an applicable needinfo target.
        bugs = {
            bug_id: bug
            for bug_id, bug in bugs.items()
            if "regressor_bug_id" in bug or "needinfo_target" in bug
        }

        return bugs

    def get_bugs(self, date="today", bug_ids=[]):
        bugs = super(FuzzingBisectionWithoutRegressedBy, self).get_bugs(
            date=date, bug_ids=bug_ids
        )
        bugs = self.find_regressor_or_needinfo_target(bugs)
        self.set_autofix(bugs)

        return bugs

if __name__ == "__main__":
    FuzzingBisectionWithoutRegressedBy().run()
<commit_msg>Fix `mypy` (#1481)<commit_after># This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.

import re
from typing import Dict

import requests
from libmozdata.bugzilla import Bugzilla, BugzillaUser

from auto_nag import logger, utils
from auto_nag.bzcleaner import BzCleaner
from auto_nag.people import People

PUSHLOG_PAT = re.compile(r"Pushlog: (.+)")
BUG_PAT = re.compile(r"[\t ]*[Bb][Uu][Gg][\t ]*([0-9]+)")

def is_ignorable_path(path: str) -> bool:
    # TODO: also ignore other kinds of files that certainly can't cause regressions.

    if any(
        path.endswith(ext) for ext in (".txt", ".md", ".rst", ".pdf", ".doc", ".otf")
    ):
        return True

    # This code was adapted from https://github.com/mozsearch/mozsearch/blob/2e24a308bf66b4c149683bfeb4ceeea3b250009a/router/router.py#L127
    if (
        "/test/" in path
        or "/tests/" in path
        or "/mochitest/" in path
        or "/unit/" in path
        or "/gtest/" in path
        or "testing/" in path
        or "/jsapi-tests/" in path
        or "/reftests/" in path
        or "/reftest/" in path
        or "/crashtests/" in path
        or "/crashtest/" in path
        or "/gtests/" in path
        or "/googletest/" in path
    ):
        return True

    return False

class FuzzingBisectionWithoutRegressedBy(BzCleaner):
    def __init__(self) -> None:
        super().__init__()
        self.people = People.get_instance()
        self.autofix_regressed_by: Dict[str, str] = {}
        self.bzmail_to_nickname: Dict[str, str] = {}

    def description(self):
        return "Bugs with a fuzzing bisection and without regressed_by"

    def handle_bug(self, bug, data):
        bugid = str(bug["id"])
        data[bugid] = {
            "assigned_to_email": bug["assigned_to"],
            "assigned_to_nickname": bug["assigned_to_detail"]["nick"],
        }
        self.bzmail_to_nickname[bug["assigned_to"]] = bug["assigned_to_detail"]["nick"]
        return bug

    def set_autofix(self, bugs):
        for bug_id, bug in bugs.items():
            if "regressor_bug_id" in bug:
                self.autofix_regressed_by[bug_id] = {
                    "comment": {
                        "body": "Setting regressed_by field after analyzing regression range found by bugmon."
                    },
                    "regressed_by": {"add": [bug["regressor_bug_id"]]},
                }
            elif "needinfo_target" in bug:
                self.add_auto_ni(
                    bug_id,
                    bug["needinfo_target"],
                )
            else:
                assert (
                    False
                ), "If we are here, we should either have a regressor or a needinfo target"

    def get_autofix_change(self) -> dict:
        return self.autofix_regressed_by

    def get_bz_params(self, date):
        return {
            "include_fields": ["assigned_to"],
            "f1": "regressed_by",
            "o1": "isempty",
            "n2": 1,
            "f2": "regressed_by",
            "o2": "everchanged",
            "n3": 1,
            "f3": "longdesc",
            "o3": "casesubstring",
            "v3": "since this bug contains a bisection range, could you fill (if possible) the regressed_by field",
            # TODO: Nag in the duplicate target instead, making sure it doesn't already have regressed_by.
            "f4": "resolution",
            "o4": "notequals",
            "v4": "DUPLICATE",
            "emaillongdesc1": "1",
            "emailtype1": "exact",
            "email1": "bugmon@mozilla.com",
        }

    def comment_handler(self, bug, bug_id, bugs):
        range_found = False
        # We start from the last comment just in case bugmon has updated the range.
        for comment in bug["comments"][::-1]:
            if (
                "BugMon: Reduced build range" not in comment["text"]
                and "The bug appears to have been introduced in the following build range"
                not in comment["text"]
            ):
                continue

            range_found = True

            # Try to parse the regression range to find the regressor or at least somebody good to needinfo.
            pushlog_match = PUSHLOG_PAT.search(comment["text"])
            url = (
                pushlog_match.group(1).replace("pushloghtml", "json-pushes")
                + "&full=1&version=2"
            )
            r = requests.get(url)
            r.raise_for_status()

            changesets = [
                changeset
                for push in r.json()["pushes"].values()
                for changeset in push["changesets"]
                if any(not is_ignorable_path(path) for path in changeset["files"])
            ]

            regressor_bug_ids = set()
            for changeset in changesets:
                bug_match = BUG_PAT.search(changeset["desc"])
                if bug_match is not None:
                    regressor_bug_ids.add(bug_match.group(1))

            if len(regressor_bug_ids) == 1:
                # Only one bug in the regression range, we are sure about the regressor!
                bugs[bug_id]["regressor_bug_id"] = regressor_bug_ids.pop()
                break

            if "needinfo_target" not in bugs[bug_id]:
                authors = set(changeset["author"] for changeset in changesets)
                # TODO: also needinfo when there is more than one author, e.g. if there are up to three (where three should be a configurable number).
                if len(authors) == 1:
                    author = authors.pop()
                    author_parts = author.split("<")
                    author_email = author_parts[1][:-1]
                    bzmail = self.people.get_bzmail_from_name(author_email)
                    if bzmail is None:
                        logger.warning(f"No bzmail for {author} in bug {bug_id}")
                        continue
                    self.bzmail_to_nickname[bzmail] = ""
                    bugs[bug_id]["needinfo_target"] = {
                        "mail": bzmail,
                        "nickname": "",
                    }
                    break

        # Exclude bugs that do not have a range found by BugMon.
        if not range_found:
            del bugs[bug_id]

    def find_regressor_or_needinfo_target(self, bugs: dict) -> dict:
        # Needinfo assignee when there is one.
        for bug in bugs.values():
            if not utils.is_no_assignee(bug["assigned_to_email"]):
                bug["needinfo_target"] = {
                    "mail": bug["assigned_to_email"],
                    "nickname": bug["assigned_to_nickname"],
                }

        Bugzilla(
            bugids=self.get_list_bugs(bugs),
            commenthandler=self.comment_handler,
            commentdata=bugs,
            comment_include_fields=["text"],
        ).get_data().wait()

        def user_handler(user: dict) -> None:
            self.bzmail_to_nickname[user["name"]] = user["nick"]

        users = set(
            bzmail
            for bzmail, nickname in self.bzmail_to_nickname.items()
            if not nickname
        )
        if len(users) > 0:
            BugzillaUser(
                user_names=list(users),
                user_handler=user_handler,
                include_fields=["name", "nick"],
            ).wait()

        for bug in bugs.values():
            if "needinfo_target" in bug and not bug["needinfo_target"]["nickname"]:
                bug["needinfo_target"]["nickname"] = self.bzmail_to_nickname[
                    bug["needinfo_target"]["mail"]
                ]

        # Exclude all bugs where we couldn't find a definite regressor bug ID or an applicable needinfo target.
        bugs = {
            bug_id: bug
            for bug_id, bug in bugs.items()
            if "regressor_bug_id" in bug or "needinfo_target" in bug
        }

        return bugs

    def get_bugs(self, date="today", bug_ids=[]):
        bugs = super(FuzzingBisectionWithoutRegressedBy, self).get_bugs(
            date=date, bug_ids=bug_ids
        )
        bugs = self.find_regressor_or_needinfo_target(bugs)
        self.set_autofix(bugs)

        return bugs

if __name__ == "__main__":
    FuzzingBisectionWithoutRegressedBy().run()
<|endoftext|>


<commit_before>__authors__ = ["Matthias Feurer"]

import numpy as np
import scipy.stats
import sklearn.cluster
import sklearn.manifold
import sklearn.preprocessing
import sklearn.utils

class GMeans(object):
    def __init__(self, minimum_samples_per_cluster=2, n_init=10, significance=4,
                 restarts=10, random_state=None, ):
        self.minimum_samples_per_cluster = minimum_samples_per_cluster
        self.n_init = n_init
        self.significance = significance
        self.restarts = restarts
        self.random_state = sklearn.utils.check_random_state(random_state)

    def fit(self, X):
        self.inertia_ = np.inf

        for i in range(self.restarts):
            KMeans = sklearn.cluster.KMeans(n_clusters=1, n_init=1,
                                            random_state=self.random_state)
            KMeans.fit(X)

            while True:
                # Splitting part
                change = False
                cluster_centers = []

                for i, cluster_center in enumerate(KMeans.cluster_centers_):
                    indices = KMeans.labels_ == i
                    X_ = X[indices]

                    for i in range(10):
                        KMeans_ = sklearn.cluster.KMeans(n_clusters=2,
                                                         n_init=self.n_init,
                                                         random_state=self.random_state)
                        predictions = KMeans_.fit_predict(X_)
                        bins = np.bincount(predictions)
                        minimum = np.min(bins)
                        if minimum >= self.minimum_samples_per_cluster:
                            break

                    if minimum <= self.minimum_samples_per_cluster:
                        cluster_centers.append(cluster_center)
                    else:
                        # Test fit:
                        centroid0, centroid1 = KMeans_.cluster_centers_
                        v = centroid1 - centroid0
                        X__prime = np.inner(v, X_) / np.linalg.norm(v, ord=2)
                        mean = np.mean(X__prime)
                        std = np.std(X__prime)
                        X__prime = (X__prime - mean) / std
                        # A2 is A^2_* from [Hamerly 2006], equation (2)
                        A2, critical, sig = scipy.stats.anderson(X__prime)

                        # Reject the split
                        if A2 < critical[self.significance]:
                            cluster_centers.append(cluster_center)
                        # Accept the split
                        else:
                            change = True
                            cluster_centers.extend(KMeans_.cluster_centers_)

                if change is False:
                    break

                # Refinement
                KMeans = sklearn.cluster.KMeans(n_clusters=len(cluster_centers), n_init=1,
                                                init=np.array(cluster_centers),
                                                random_state=self.random_state)
                KMeans.fit(X)

            if KMeans.inertia_ < self.inertia_:
                self.KMeans = KMeans
                self.inertia_ = self.KMeans.inertia_

        self.cluster_centers_ = self.KMeans.cluster_centers_
        self.labels_ = self.KMeans.labels_
        self.inertia_ = self.KMeans.inertia_

    def fit_predict(self, X):
        self.fit(X)
        predictions = self.KMeans.predict(X)
        return predictions
<commit_msg>Add size check before trying to split for GMeans (#732)<commit_after>__authors__ = ["Matthias Feurer"]

import numpy as np
import scipy.stats
import sklearn.cluster
import sklearn.manifold
import sklearn.preprocessing
import sklearn.utils

class GMeans(object):
    def __init__(self, minimum_samples_per_cluster=2, n_init=10, significance=4,
                 restarts=10, random_state=None, ):
        self.minimum_samples_per_cluster = minimum_samples_per_cluster
        self.n_init = n_init
        self.significance = significance
        self.restarts = restarts
        self.random_state = sklearn.utils.check_random_state(random_state)

    def fit(self, X):
        self.inertia_ = np.inf

        for i in range(self.restarts):
            KMeans = sklearn.cluster.KMeans(n_clusters=1, n_init=1,
                                            random_state=self.random_state)
            KMeans.fit(X)

            while True:
                # Splitting part
                change = False
                cluster_centers = []

                for i, cluster_center in enumerate(KMeans.cluster_centers_):
                    indices = KMeans.labels_ == i
                    X_ = X[indices]

                    if np.sum(indices) < self.minimum_samples_per_cluster*2:
                        cluster_centers.append(cluster_center)
                        continue

                    for i in range(10):
                        KMeans_ = sklearn.cluster.KMeans(n_clusters=2,
                                                         n_init=self.n_init,
                                                         random_state=self.random_state)
                        predictions = KMeans_.fit_predict(X_)
                        bins = np.bincount(predictions)
                        minimum = np.min(bins)
                        if minimum >= self.minimum_samples_per_cluster:
                            break

                    if minimum <= self.minimum_samples_per_cluster:
                        cluster_centers.append(cluster_center)
                    else:
                        # Test fit:
                        centroid0, centroid1 = KMeans_.cluster_centers_
                        v = centroid1 - centroid0
                        X__prime = np.inner(v, X_) / np.linalg.norm(v, ord=2)
                        mean = np.mean(X__prime)
                        std = np.std(X__prime)
                        X__prime = (X__prime - mean) / std
                        # A2 is A^2_* from [Hamerly 2006], equation (2)
                        A2, critical, sig = scipy.stats.anderson(X__prime)

                        # Reject the split
                        if A2 < critical[self.significance]:
                            cluster_centers.append(cluster_center)
                        # Accept the split
                        else:
                            change = True
                            cluster_centers.extend(KMeans_.cluster_centers_)

                if change is False:
                    break

                # Refinement
                KMeans = sklearn.cluster.KMeans(n_clusters=len(cluster_centers), n_init=1,
                                                init=np.array(cluster_centers),
                                                random_state=self.random_state)
                KMeans.fit(X)

            if KMeans.inertia_ < self.inertia_:
                self.KMeans = KMeans
                self.inertia_ = self.KMeans.inertia_

        self.cluster_centers_ = self.KMeans.cluster_centers_
        self.labels_ = self.KMeans.labels_
        self.inertia_ = self.KMeans.inertia_

    def fit_predict(self, X):
        self.fit(X)
        predictions = self.KMeans.predict(X)
        return predictions
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-

# Copyright (c) 2008/2009 Andrey Vlasovskikh
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

'''Парсер рекурсивного спуска на основе функциональных комбинаторов.

Основные комбинаторы парсеров взяты из книги Harrison. J. "Introduction to
Functional Programming" и переписаны с ML на Python. См. также
<http://code.google.com/p/funprog-ru/>.

Парсер `p` представляется функцией типа:

    p :: Sequence(a) -> (State -> ((b, Sequence(a)), State))'

принимающей последовательность токенов произвольного типа `a` и возвращающей
функцию, принимающую состояние потока разбора и возвращаюую пару из пары
(разобранный токен произвольного типа `b`, последовательность неразобранных
токенов) и нового состояния разбора.

Далее в библиотеке используется синоним типа этой функции `Parser(a, b)`.

TBD: В дальнейшем тип, возможно, будет упрощён до:

    p(tokens[, state]) -> (value, rest, newstate)
    p :: Sequence(a), State? -> (b, Sequence(a), State)

Функции парсеров заворачиваются в объект `Parser`, для которого определны
операторы `+` для последовательного выполнения двух парсеров, `|` для выбора
второго парсера в случае ошибки разбора в первом, `>>` для преобразования
значения, полученного при разборе.

Для упрощения задачи синтаксического анализа и для получения более читаемых
сообщений об ошибках рекомендуется в качестве токенов использовать объекты с
выхода токенизатора, уже включающие в себя, например, информацию о позиции в
коде. В библиотеку входит модуль `funcparserlib.lexer`, для токенизации на
основе регулярных выражений.

Для вывода отладочных сообщений библиотека использует логгер из logging с именем
`"funcparserlib"`.
'''

__all__ = ['Parser', 'NoParseError', 'State', 'finished', 'many', 'some', 'a',
        'several', 'maybe', 'skip', 'oneplus']

import logging

log = logging.getLogger('funcparserlib')

class State(object):
    '''Состояние разбора потока токенов.

    Хранит текущую позицию в потоке pos и максимально просмотренную позицию max.
    '''
    def __init__(self, pos=0, max=0):
        self.pos = pos
        self.max = max

    def __unicode__(self):
        return unicode((self.pos, self.max))

    def __repr__(self):
        return 'State(%r, %r)' % (self.pos, self.max)

class NoParseError(Exception):
    def __init__(self, msg='', state=None):
        self.msg = msg
        self.state = state

    def __unicode__(self):
        return self.msg

class Parser(object):
    def __init__(self, p, name=None):
        self.wrapped = p
        if name is not None:
            self.name = name
        else:
            self.name = p.__doc__

    def named(self, name):
        'Задаёт имя парсера для более читаемых сообщений.'
        self.name = name
        return self

    def __call__(self, tokens):
        'Sequence(a) -> (State -> ((b, Sequence(a)), State))'
        log.debug('trying rule "%s"' % self.name)
        return lambda s: self.wrapped(tokens)(s)

    def parse(self, tokens):
        '''Sequence(a) -> b

        Вызывает парсер, возвращая только результат разбора. Повышает читаемость
        ошибок разбора за счёт вывода максимальной просмотренной позиции в
        потоке.
        '''
        try:
            ((tree, _), _) = self(tokens)(State())
            return tree
        except NoParseError, e:
            max = e.state.max
            pos = tokens[max] if len(tokens) > max else '<EOF>'
            raise NoParseError(u'%s: %s' % (e.msg, pos))

    def __add__(self, other):
        '''Parser(a, b), Parser(a, c) -> Parser(a, _Tuple(b, c)*)

        Последовательная композиция парсеров.

        NOTE: Настоящий тип разобранного результата не всегда такой.
        Статический тип указать нельзя.
        '''
        @Parser
        def f(tokens):
            def g(s):
                (v1, r1), s2 = self(tokens)(s)
                (v2, r2), s3 = other(r1)(s2)
                vs = [v for v in [v1, v2] if not isinstance(v, _Ignored)]
                if len(vs) == 1:
                    t = vs[0]
                elif len(vs) == 2 and isinstance(vs[0], _Tuple):
                    t = _Tuple(v1 + (v2,))
                else:
                    t = _Tuple(vs)
                return ((t, r2), s3)
            return g
        f.name = '(%s, %s)' % (self.name, other.name)
        return f

    def __or__(self, other):
        '''Parser(a, b), Parser(a, c) -> Parser(a, b | c)

        Вариант выбора из двух парсеров.'''
        @Parser
        def f(tokens):
            def g(s):
                try:
                    return self(tokens)(s)
                except NoParseError, e:
                    return other(tokens)(State(s.pos, e.state.max))
            return g
        f.name = '(%s | %s)' % (self.name, other.name)
        return f

    def __rshift__(self, f):
        'Parser(a, b), (b -> c) -> Parser(a, c)'
        @Parser
        def g(tokens):
            def h(s):
                (v, r), s2 = self(tokens)(s)
                return ((f(v), r), s2)
            return h
        g.name = '%s >> %s' % (self.name, f.__doc__ or '...')
        return g

class _Tuple(tuple): pass

class _Ignored(object):
    def __init__(self, value):
        self.value = value

@Parser
def finished(tokens):
    '''Parser(a, None)

    Выбрасывает ошибку, если в потоке токенов хоть что-то осталось.
    '''
    def f(s):
        if len(tokens) == 0:
            return ((None, tokens), s)
        else:
            raise NoParseError('should have reached eof', s)
    return f
finished.name = 'finished'

def many(p):
    '''Parser(a, b) -> Parser(a, [b])

    Возвращает пасрсер, многократно применяющий парсер p к потоку, пока p
    успешно разбирает токены, и возвращающий список разобранных значений.
    '''
    @Parser
    def f(tokens):
        def g(s):
            try:
                (v, next), s2 = p(tokens)(s)
                (vs, rest), s3 = many(p)(next)(s2)
                return (([v] + vs, rest), s3)
            except NoParseError, e:
                return (([], tokens), e.state)
        return g
    f.name = '%s+' % p.name
    return f

def some(pred):
    '''(a -> bool) -> Parser(a, a)

    Возвращает парсер, разбирающий токен, который удовлетворяет предикату
    pred.
    '''
    @Parser
    def f(tokens):
        def g(s):
            if len(tokens) == 0:
                raise NoParseError('no tokens left in the stream', s)
            else:
                t, ts = tokens[0], tokens[1:]
                if pred(t):
                    pos = s.pos + 1
                    log.debug(u'*matched* "%s", new state = %s' % (
                        t, State(pos, max(pos, s.max))))
                    return ((t, ts), State(pos, max(pos, s.max)))
                else:
                    log.debug(u'failed "%s", state = %s' % (t, s))
                    raise NoParseError('got unexpected token', s)
        return g
    f.name = '(some ...)'
    return f

def a(value):
    '''a -> Parser(a, a)

    Возвращает парсер, разбирающий токен, равный value.
    '''
    return some(lambda t: t == value).named('(a "%s")' % value)

def several(pred):
    '''(a -> bool) -> Parser(a, [a])

    Возвращает парсер, разбирающий последовательность токенов,
    удовлетворяющих предикату pred.
    '''
    return many(some(pred))

def maybe(p):
    '''Parser(a, b) -> Parser(a, b | None)

    Возвращает парсер, который при ошибке возвращает None.
    '''
    @Parser
    def f(tokens):
        def g(s):
            try:
                return p(tokens)(s)
            except NoParseError, e:
                return ((None, tokens), e.state)
        return g
    f.name = '(maybe %s)' % p.name
    return f

def skip(p):
    '''Parser(a, b) -> _Ignored(b)

    Возвращает парсер, результаты работы которого игнорируются комбинатором
    последовательной композиции +.
    '''
    return p >> (lambda x: _Ignored(x))

def oneplus(p):
    '''Parser(a, b) -> Parser(a, [b])

    Возвращает парсер, разбирающий одно или более вхождение токена.
    '''
    return p + many(p) >> (lambda x: [x[0]] + x[1])

<commit_msg>Uncurried parser function type in order to simplify things<commit_after># -*- coding: utf-8 -*-

# Copyright (c) 2008/2009 Andrey Vlasovskikh
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

'''Парсер рекурсивного спуска на основе функциональных комбинаторов.

Основные комбинаторы парсеров взяты из книги Harrison. J. "Introduction to
Functional Programming" и переписаны с ML на Python. См. также
<http://code.google.com/p/funprog-ru/>.

Парсер `p` представляется функцией типа:

    p :: Sequence(a), State -> (b, Sequence(a), State)

принимающей последовательность токенов произвольного типа `a` и возвращающей
функцию, принимающую состояние потока разбора и возвращаюую пару из пары
(разобранный токен произвольного типа `b`, последовательность неразобранных
токенов) и нового состояния разбора.

Далее в библиотеке используется синоним типа этой функции `Parser(a, b)`.

Функции парсеров заворачиваются в объект `Parser`, для которого определны
операторы `+` для последовательного выполнения двух парсеров, `|` для выбора
второго парсера в случае ошибки разбора в первом, `>>` для преобразования
значения, полученного при разборе.

Для упрощения задачи синтаксического анализа и для получения более читаемых
сообщений об ошибках рекомендуется в качестве токенов использовать объекты с
выхода токенизатора, уже включающие в себя, например, информацию о позиции в
коде. В библиотеку входит модуль `funcparserlib.lexer`, для токенизации на
основе регулярных выражений.

Для вывода отладочных сообщений библиотека использует логгер из logging с именем
`"funcparserlib"`.
'''

__all__ = ['Parser', 'NoParseError', 'State', 'finished', 'many', 'some', 'a',
        'several', 'maybe', 'skip', 'oneplus']

import logging

log = logging.getLogger('funcparserlib')

class State(object):
    '''Состояние разбора потока токенов.

    Хранит текущую позицию в потоке pos и максимально просмотренную позицию max.
    '''
    def __init__(self, pos=0, max=0):
        self.pos = pos
        self.max = max

    def __unicode__(self):
        return unicode((self.pos, self.max))

    def __repr__(self):
        return 'State(%r, %r)' % (self.pos, self.max)

class NoParseError(Exception):
    def __init__(self, msg='', state=None):
        self.msg = msg
        self.state = state

    def __unicode__(self):
        return self.msg

class Parser(object):
    def __init__(self, p, name=None):
        self.wrapped = p
        if name is not None:
            self.name = name
        else:
            self.name = p.__doc__

    def named(self, name):
        'Задаёт имя парсера для более читаемых сообщений.'
        self.name = name
        return self

    def __call__(self, tokens, s):
        'Sequence(a), State -> (b, Sequence(a), State)'
        log.debug('trying rule "%s"' % self.name)
        return self.wrapped(tokens, s)

    def parse(self, tokens):
        '''Sequence(a) -> b

        Вызывает парсер, возвращая только результат разбора. Повышает читаемость
        ошибок разбора за счёт вывода максимальной просмотренной позиции в
        потоке.
        '''
        try:
            (tree, _, _) = self(tokens, State())
            return tree
        except NoParseError, e:
            max = e.state.max
            pos = tokens[max] if len(tokens) > max else '<EOF>'
            raise NoParseError(u'%s: %s' % (e.msg, pos))

    def __add__(self, other):
        '''Parser(a, b), Parser(a, c) -> Parser(a, _Tuple(b, c)*)

        Последовательная композиция парсеров.

        NOTE: Настоящий тип разобранного результата не всегда такой.
        Статический тип указать нельзя.
        '''
        @Parser
        def f(tokens, s):
            (v1, r1, s2) = self(tokens, s)
            (v2, r2, s3) = other(r1, s2)
            vs = [v for v in [v1, v2] if not isinstance(v, _Ignored)]
            if len(vs) == 1:
                t = vs[0]
            elif len(vs) == 2 and isinstance(vs[0], _Tuple):
                t = _Tuple(v1 + (v2,))
            else:
                t = _Tuple(vs)
            return (t, r2, s3)
        f.name = '(%s, %s)' % (self.name, other.name)
        return f

    def __or__(self, other):
        '''Parser(a, b), Parser(a, c) -> Parser(a, b | c)

        Вариант выбора из двух парсеров.'''
        @Parser
        def f(tokens, s):
            try:
                return self(tokens, s)
            except NoParseError, e:
                return other(tokens, State(s.pos, e.state.max))
        f.name = '(%s | %s)' % (self.name, other.name)
        return f

    def __rshift__(self, f):
        'Parser(a, b), (b -> c) -> Parser(a, c)'
        @Parser
        def g(tokens, s):
            (v, r, s2) = self(tokens, s)
            return (f(v), r, s2)
        g.name = '%s >> %s' % (self.name, f.__doc__ or '...')
        return g

class _Tuple(tuple): pass

class _Ignored(object):
    def __init__(self, value):
        self.value = value

@Parser
def finished(tokens, s):
    '''Parser(a, None)

    Выбрасывает ошибку, если в потоке токенов хоть что-то осталось.
    '''
    if len(tokens) == 0:
        return (None, tokens, s)
    else:
        raise NoParseError('should have reached eof', s)
finished.name = 'finished'

def many(p):
    '''Parser(a, b) -> Parser(a, [b])

    Возвращает пасрсер, многократно применяющий парсер p к потоку, пока p
    успешно разбирает токены, и возвращающий список разобранных значений.
    '''
    @Parser
    def f(tokens, s):
        try:
            (v, next, s2) = p(tokens, s)
            (vs, rest, s3) = many(p)(next, s2)
            return ([v] + vs, rest, s3)
        except NoParseError, e:
            return ([], tokens, e.state)
    f.name = '%s+' % p.name
    return f

def some(pred):
    '''(a -> bool) -> Parser(a, a)

    Возвращает парсер, разбирающий токен, который удовлетворяет предикату
    pred.
    '''
    @Parser
    def f(tokens, s):
        if len(tokens) == 0:
            raise NoParseError('no tokens left in the stream', s)
        else:
            t, ts = tokens[0], tokens[1:]
            if pred(t):
                pos = s.pos + 1
                log.debug(u'*matched* "%s", new state = %s' % (
                    t, State(pos, max(pos, s.max))))
                return (t, ts, State(pos, max(pos, s.max)))
            else:
                log.debug(u'failed "%s", state = %s' % (t, s))
                raise NoParseError('got unexpected token', s)
    f.name = '(some ...)'
    return f

def a(value):
    '''a -> Parser(a, a)

    Возвращает парсер, разбирающий токен, равный value.
    '''
    return some(lambda t: t == value).named('(a "%s")' % value)

def several(pred):
    '''(a -> bool) -> Parser(a, [a])

    Возвращает парсер, разбирающий последовательность токенов,
    удовлетворяющих предикату pred.
    '''
    return many(some(pred))

def maybe(p):
    '''Parser(a, b) -> Parser(a, b | None)

    Возвращает парсер, который при ошибке возвращает None.
    '''
    @Parser
    def f(tokens, s):
        try:
            return p(tokens, s)
        except NoParseError, e:
            return (None, tokens, e.state)
    f.name = '(maybe %s)' % p.name
    return f

def skip(p):
    '''Parser(a, b) -> _Ignored(b)

    Возвращает парсер, результаты работы которого игнорируются комбинатором
    последовательной композиции +.
    '''
    return p >> (lambda x: _Ignored(x))

def oneplus(p):
    '''Parser(a, b) -> Parser(a, [b])

    Возвращает парсер, разбирающий одно или более вхождение токена.
    '''
    return p + many(p) >> (lambda x: [x[0]] + x[1])

<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
##############################################################################
#
#    OpenERP, Open Source Management Solution
#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

from osv import osv
from tools.translate import _

class base_module_configuration(osv.osv_memory):

    _name = "base.module.configuration"

    def start(self, cr, uid, ids, context=None):
        todo_ids = self.pool.get('ir.actions.todo').search(cr, uid, ['|', ('state', '=', 'open'), ('restart', '=', 'always')])
        if not todo_ids:
            # When there is no wizard todo it will display message
            data_obj = self.pool.get('ir.model.data')
            result = data_obj._get_id(cr, uid, 'base', 'view_base_module_configuration_form')
            view_id = data_obj.browse(cr, uid, result).res_id
            value = {
                    'name': _('System Configuration done'),
                    'view_type': 'form',
                    'view_mode': 'form',
                    'res_model': 'base.module.configuration',
                    'view_id': [view_id],
                    'type': 'ir.actions.act_window',
                    'target': 'new'
                }
            return value
        # Run the config wizards
        config_pool = self.pool.get('res.config')
        return config_pool.start(cr, uid, ids, context=context)

base_module_configuration()

# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
<commit_msg>[IMP]: Improvement in domain to launch config wizard by wizard<commit_after># -*- coding: utf-8 -*-
##############################################################################
#
#    OpenERP, Open Source Management Solution
#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

from osv import osv
from tools.translate import _

class base_module_configuration(osv.osv_memory):

    _name = "base.module.configuration"

    def start(self, cr, uid, ids, context=None):
        todo_ids = self.pool.get('ir.actions.todo').search(cr, uid, ['|', ('state', '=', 'open'), '&', ('state', '=', 'skip'), ('restart', '=', 'onskip')])
        if not todo_ids:
            # When there is no wizard todo it will display message
            data_obj = self.pool.get('ir.model.data')
            result = data_obj._get_id(cr, uid, 'base', 'view_base_module_configuration_form')
            view_id = data_obj.browse(cr, uid, result).res_id
            value = {
                    'name': _('System Configuration done'),
                    'view_type': 'form',
                    'view_mode': 'form',
                    'res_model': 'base.module.configuration',
                    'view_id': [view_id],
                    'type': 'ir.actions.act_window',
                    'target': 'new'
                }
            return value
        # Run the config wizards
        config_pool = self.pool.get('res.config')
        return config_pool.start(cr, uid, ids, context=context)

base_module_configuration()

# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
<|endoftext|>


<commit_before>import atexit
import logging
import os
import threading

import cherrypy
import ifcfg
import requests
from django.conf import settings
from django.core.management import ManagementUtility
from kolibri.content.utils import paths

from .system import kill_pid, pid_exists

logger = logging.getLogger(__name__)

# Status codes for kolibri
STATUS_RUNNING = 0
STATUS_STOPPED = 1
STATUS_STARTING_UP = 4
STATUS_NOT_RESPONDING = 5
STATUS_FAILED_TO_START = 6
STATUS_UNCLEAN_SHUTDOWN = 7
STATUS_UNKNOWN_INSTANCE = 8
STATUS_SERVER_CONFIGURATION_ERROR = 9
STATUS_PID_FILE_READ_ERROR = 99
STATUS_PID_FILE_INVALID = 100
STATUS_UNKNOWN = 101

# Used to store PID and port number (both in foreground and daemon mode)
PID_FILE = os.path.join(os.environ['KOLIBRI_HOME'], "server.pid")

# Used to PID, port during certain exclusive startup process, before we fork
# to daemon mode
STARTUP_LOCK = os.path.join(os.environ['KOLIBRI_HOME'], "server.lock")

# This is a special file with daemon activity. It logs ALL stderr output, some
# might not have made it to the log file!
DAEMON_LOG = os.path.join(os.environ['KOLIBRI_HOME'], "server.log")

# Currently non-configurable until we know how to properly handle this
LISTEN_ADDRESS = "0.0.0.0"

class NotRunning(Exception):
    """
    Raised when server was expected to run, but didn't. Contains a status
    code explaining why.
    """

    def __init__(self, status_code):
        self.status_code = status_code
        super(NotRunning, self).__init__()

def start(port=8080):
    """
    Starts the server.

    :param: port: Port number (default: 8080)
    """

    # start the pingback thread
    PingbackThread.start_command()

    # Write the new PID
    with open(PID_FILE, 'w') as f:
        f.write("%d\n%d" % (os.getpid(), port))

    # This should be run every time the server is started for now.
    # Events to trigger it are hard, because of copying a content folder into
    # ~/.kolibri, or deleting a channel DB on disk
    from kolibri.content.utils.annotation import update_channel_metadata
    update_channel_metadata()

    # This is also run every time the server is started to clear all the tasks
    # in the queue
    from kolibri.tasks.client import get_client
    get_client().clear(force=True)

    def rm_pid_file():
        os.unlink(PID_FILE)

    atexit.register(rm_pid_file)

    run_server(port=port)

class PingbackThread(threading.Thread):

    @classmethod
    def start_command(cls):
        thread = cls()
        thread.daemon = True
        thread.start()

    def run(self):
        command = ManagementUtility().fetch_command("ping")
        command.execute(server="http://telemetry.learningequality.org/api/v1/pingback", interval=0.2, checkrate=0.1)

def stop(pid=None, force=False):
    """
    Stops the kalite server, either from PID or through a management command

    :param args: List of options to parse to the django management command
    :raises: NotRunning
    """

    if not force:
        # Kill the KA lite server
        kill_pid(pid)
    else:
        try:
            pid, __ = _read_pid_file(PID_FILE)
            kill_pid(pid)
        except ValueError:
            logger.error("Could not find PID in .pid file\n")
        except OSError:
            logger.error("Could not read .pid file\n")

    # TODO: Check that server has in fact been killed, otherwise we should
    # raise an error...

    # Finally, remove the PID file
    os.unlink(PID_FILE)

def run_server(port):

    # Mount the application
    from kolibri.deployment.default.wsgi import application
    cherrypy.tree.graft(application, "/")

    cherrypy.config.update({"environment": "production"})

    serve_static_dir(settings.STATIC_ROOT, settings.STATIC_URL)
    serve_static_dir(settings.CONTENT_DATABASE_DIR,
                     paths.get_content_database_url("/"))
    serve_static_dir(settings.CONTENT_STORAGE_DIR,
                     paths.get_content_storage_url("/"))

    # Unsubscribe the default server
    cherrypy.server.unsubscribe()

    # Instantiate a new server object
    server = cherrypy._cpserver.Server()

    # Configure the server
    server.socket_host = LISTEN_ADDRESS
    server.socket_port = port
    server.thread_pool = 30

    # Subscribe this server
    server.subscribe()

    # Start the server engine (Option 1 *and* 2)
    cherrypy.engine.start()
    cherrypy.engine.block()

def serve_static_dir(root, url):

    static_handler = cherrypy.tools.staticdir.handler(
        section="/",
        dir=os.path.split(root)[1],
        root=os.path.abspath(os.path.split(root)[0]))
    cherrypy.tree.mount(static_handler, url)

def _read_pid_file(filename):
    """
    Reads a pid file and returns the contents. PID files have 1 or 2 lines;
     - first line is always the pid
     - optional second line is the port the server is listening on.

    :param filename: Path of PID to read
    :return: (pid, port): with the PID in the file and the port number
                          if it exists. If the port number doesn't exist, then
                          port is None.
    """
    pid_file_lines = open(filename, "r").readlines()

    if len(pid_file_lines) == 2:
        pid, port = pid_file_lines
        pid, port = int(pid), int(port)
    elif len(pid_file_lines) == 1:
        # The file only had one line
        pid, port = int(pid_file_lines[0]), None
    else:
        raise ValueError("PID file must have 1 or two lines")

    return pid, port

def _write_pid_file(filename, port):
    """
    Writes a PID file in the format Kolibri parses

    :param: filename: Path of file to write
    :param: port: Listening port number which the server is assigned
    """

    with open(filename, 'w') as f:
        f.write("%d\n%d" % (os.getpid(), port))

def get_status():  # noqa: max-complexity=16
    """
    Tries to get the PID of a running server.

    The behavior is also quite redundant given that `kalite start` should
    always create a PID file, and if its been started directly with the
    runserver command, then its up to the developer to know what's happening.
    :returns: (PID, address, port), where address is not currently detected in
              a valid way because it's not configurable, and we might be
              listening on several IPs.
    :raises: NotRunning
    """

    # There is no PID file (created by server daemon)
    if not os.path.isfile(PID_FILE):
        # Is there a startup lock?
        if os.path.isfile(STARTUP_LOCK):
            try:
                pid, port = _read_pid_file(STARTUP_LOCK)
                # Does the PID in there still exist?
                if pid_exists(pid):
                    raise NotRunning(STATUS_STARTING_UP)
                # It's dead so assuming the startup went badly
                else:
                    raise NotRunning(STATUS_FAILED_TO_START)
            # Couldn't parse to int or empty PID file
            except (TypeError, ValueError):
                raise NotRunning(STATUS_STOPPED)
        raise NotRunning(STATUS_STOPPED)  # Stopped

    # PID file exists, check if it is running
    try:
        pid, port = _read_pid_file(PID_FILE)
    except (ValueError, OSError):
        raise NotRunning(STATUS_PID_FILE_INVALID)  # Invalid PID file

    # PID file exists, but process is dead
    if pid is None or not pid_exists(pid):
        if os.path.isfile(STARTUP_LOCK):
            raise NotRunning(STATUS_FAILED_TO_START)  # Failed to start
        raise NotRunning(STATUS_UNCLEAN_SHUTDOWN)  # Unclean shutdown

    listen_port = port

    try:
        # Timeout is 3 seconds, we don't want the status command to be slow
        # TODO: Using 127.0.0.1 is a hardcode default from KA Lite, it could
        # be configurable
        # TODO: HTTP might not be the protocol if server has SSL
        response = requests.get(
            "http://{}:{}".format("127.0.0.1", listen_port), timeout=3)
    except (requests.exceptions.ReadTimeout,
            requests.exceptions.ConnectionError):
        raise NotRunning(STATUS_NOT_RESPONDING)
    except (requests.exceptions.RequestException):
        if os.path.isfile(STARTUP_LOCK):
            raise NotRunning(STATUS_STARTING_UP)  # Starting up
        raise NotRunning(STATUS_UNCLEAN_SHUTDOWN)

    if response.status_code == 404:
        raise NotRunning(STATUS_UNKNOWN_INSTANCE)  # Unknown HTTP server

    if response.status_code != 200:
        # Probably a mis-configured kolibri
        raise NotRunning(STATUS_SERVER_CONFIGURATION_ERROR)

    return pid, LISTEN_ADDRESS, listen_port  # Correct PID !

    # We don't detect this at present:
    # Could be detected because we fetch the PID directly via HTTP, but this
    # is dumb because kolibri could be running in a worker pool with different
    # PID from the PID file..
    # raise NotRunning(STATUS_UNKNOWN_INSTANCE)
    # This would be the fallback when we know it's not running, but we can't
    # give a proper reason...
    # raise NotRunning(STATUS_UNKNOW)

def get_urls(listen_port=None):
    """
    :param listen_port: if set, will not try to determine the listen port from
                        other running instances.
    """
    try:
        if listen_port:
            port = listen_port
        else:
            __, __, port = get_status()
        urls = []
        interfaces = ifcfg.interfaces()
        for interface in filter(lambda i: i['inet'], interfaces.values()):
            urls.append("http://{}:{}/".format(interface['inet'], port))
        return STATUS_RUNNING, urls
    except NotRunning as e:
        return e.status_code, []
<commit_msg>decrease frequency<commit_after>import atexit
import logging
import os
import threading

import cherrypy
import ifcfg
import requests
from django.conf import settings
from django.core.management import ManagementUtility
from kolibri.content.utils import paths

from .system import kill_pid, pid_exists

logger = logging.getLogger(__name__)

# Status codes for kolibri
STATUS_RUNNING = 0
STATUS_STOPPED = 1
STATUS_STARTING_UP = 4
STATUS_NOT_RESPONDING = 5
STATUS_FAILED_TO_START = 6
STATUS_UNCLEAN_SHUTDOWN = 7
STATUS_UNKNOWN_INSTANCE = 8
STATUS_SERVER_CONFIGURATION_ERROR = 9
STATUS_PID_FILE_READ_ERROR = 99
STATUS_PID_FILE_INVALID = 100
STATUS_UNKNOWN = 101

# Used to store PID and port number (both in foreground and daemon mode)
PID_FILE = os.path.join(os.environ['KOLIBRI_HOME'], "server.pid")

# Used to PID, port during certain exclusive startup process, before we fork
# to daemon mode
STARTUP_LOCK = os.path.join(os.environ['KOLIBRI_HOME'], "server.lock")

# This is a special file with daemon activity. It logs ALL stderr output, some
# might not have made it to the log file!
DAEMON_LOG = os.path.join(os.environ['KOLIBRI_HOME'], "server.log")

# Currently non-configurable until we know how to properly handle this
LISTEN_ADDRESS = "0.0.0.0"

class NotRunning(Exception):
    """
    Raised when server was expected to run, but didn't. Contains a status
    code explaining why.
    """

    def __init__(self, status_code):
        self.status_code = status_code
        super(NotRunning, self).__init__()

def start(port=8080):
    """
    Starts the server.

    :param: port: Port number (default: 8080)
    """

    # start the pingback thread
    PingbackThread.start_command()

    # Write the new PID
    with open(PID_FILE, 'w') as f:
        f.write("%d\n%d" % (os.getpid(), port))

    # This should be run every time the server is started for now.
    # Events to trigger it are hard, because of copying a content folder into
    # ~/.kolibri, or deleting a channel DB on disk
    from kolibri.content.utils.annotation import update_channel_metadata
    update_channel_metadata()

    # This is also run every time the server is started to clear all the tasks
    # in the queue
    from kolibri.tasks.client import get_client
    get_client().clear(force=True)

    def rm_pid_file():
        os.unlink(PID_FILE)

    atexit.register(rm_pid_file)

    run_server(port=port)

class PingbackThread(threading.Thread):

    @classmethod
    def start_command(cls):
        thread = cls()
        thread.daemon = True
        thread.start()

    def run(self):
        command = ManagementUtility().fetch_command("ping")
        command.execute(server="http://telemetry.learningequality.org/api/v1/pingback", interval=2, checkrate=1)

def stop(pid=None, force=False):
    """
    Stops the kalite server, either from PID or through a management command

    :param args: List of options to parse to the django management command
    :raises: NotRunning
    """

    if not force:
        # Kill the KA lite server
        kill_pid(pid)
    else:
        try:
            pid, __ = _read_pid_file(PID_FILE)
            kill_pid(pid)
        except ValueError:
            logger.error("Could not find PID in .pid file\n")
        except OSError:
            logger.error("Could not read .pid file\n")

    # TODO: Check that server has in fact been killed, otherwise we should
    # raise an error...

    # Finally, remove the PID file
    os.unlink(PID_FILE)

def run_server(port):

    # Mount the application
    from kolibri.deployment.default.wsgi import application
    cherrypy.tree.graft(application, "/")

    cherrypy.config.update({"environment": "production"})

    serve_static_dir(settings.STATIC_ROOT, settings.STATIC_URL)
    serve_static_dir(settings.CONTENT_DATABASE_DIR,
                     paths.get_content_database_url("/"))
    serve_static_dir(settings.CONTENT_STORAGE_DIR,
                     paths.get_content_storage_url("/"))

    # Unsubscribe the default server
    cherrypy.server.unsubscribe()

    # Instantiate a new server object
    server = cherrypy._cpserver.Server()

    # Configure the server
    server.socket_host = LISTEN_ADDRESS
    server.socket_port = port
    server.thread_pool = 30

    # Subscribe this server
    server.subscribe()

    # Start the server engine (Option 1 *and* 2)
    cherrypy.engine.start()
    cherrypy.engine.block()

def serve_static_dir(root, url):

    static_handler = cherrypy.tools.staticdir.handler(
        section="/",
        dir=os.path.split(root)[1],
        root=os.path.abspath(os.path.split(root)[0]))
    cherrypy.tree.mount(static_handler, url)

def _read_pid_file(filename):
    """
    Reads a pid file and returns the contents. PID files have 1 or 2 lines;
     - first line is always the pid
     - optional second line is the port the server is listening on.

    :param filename: Path of PID to read
    :return: (pid, port): with the PID in the file and the port number
                          if it exists. If the port number doesn't exist, then
                          port is None.
    """
    pid_file_lines = open(filename, "r").readlines()

    if len(pid_file_lines) == 2:
        pid, port = pid_file_lines
        pid, port = int(pid), int(port)
    elif len(pid_file_lines) == 1:
        # The file only had one line
        pid, port = int(pid_file_lines[0]), None
    else:
        raise ValueError("PID file must have 1 or two lines")

    return pid, port

def _write_pid_file(filename, port):
    """
    Writes a PID file in the format Kolibri parses

    :param: filename: Path of file to write
    :param: port: Listening port number which the server is assigned
    """

    with open(filename, 'w') as f:
        f.write("%d\n%d" % (os.getpid(), port))

def get_status():  # noqa: max-complexity=16
    """
    Tries to get the PID of a running server.

    The behavior is also quite redundant given that `kalite start` should
    always create a PID file, and if its been started directly with the
    runserver command, then its up to the developer to know what's happening.
    :returns: (PID, address, port), where address is not currently detected in
              a valid way because it's not configurable, and we might be
              listening on several IPs.
    :raises: NotRunning
    """

    # There is no PID file (created by server daemon)
    if not os.path.isfile(PID_FILE):
        # Is there a startup lock?
        if os.path.isfile(STARTUP_LOCK):
            try:
                pid, port = _read_pid_file(STARTUP_LOCK)
                # Does the PID in there still exist?
                if pid_exists(pid):
                    raise NotRunning(STATUS_STARTING_UP)
                # It's dead so assuming the startup went badly
                else:
                    raise NotRunning(STATUS_FAILED_TO_START)
            # Couldn't parse to int or empty PID file
            except (TypeError, ValueError):
                raise NotRunning(STATUS_STOPPED)
        raise NotRunning(STATUS_STOPPED)  # Stopped

    # PID file exists, check if it is running
    try:
        pid, port = _read_pid_file(PID_FILE)
    except (ValueError, OSError):
        raise NotRunning(STATUS_PID_FILE_INVALID)  # Invalid PID file

    # PID file exists, but process is dead
    if pid is None or not pid_exists(pid):
        if os.path.isfile(STARTUP_LOCK):
            raise NotRunning(STATUS_FAILED_TO_START)  # Failed to start
        raise NotRunning(STATUS_UNCLEAN_SHUTDOWN)  # Unclean shutdown

    listen_port = port

    try:
        # Timeout is 3 seconds, we don't want the status command to be slow
        # TODO: Using 127.0.0.1 is a hardcode default from KA Lite, it could
        # be configurable
        # TODO: HTTP might not be the protocol if server has SSL
        response = requests.get(
            "http://{}:{}".format("127.0.0.1", listen_port), timeout=3)
    except (requests.exceptions.ReadTimeout,
            requests.exceptions.ConnectionError):
        raise NotRunning(STATUS_NOT_RESPONDING)
    except (requests.exceptions.RequestException):
        if os.path.isfile(STARTUP_LOCK):
            raise NotRunning(STATUS_STARTING_UP)  # Starting up
        raise NotRunning(STATUS_UNCLEAN_SHUTDOWN)

    if response.status_code == 404:
        raise NotRunning(STATUS_UNKNOWN_INSTANCE)  # Unknown HTTP server

    if response.status_code != 200:
        # Probably a mis-configured kolibri
        raise NotRunning(STATUS_SERVER_CONFIGURATION_ERROR)

    return pid, LISTEN_ADDRESS, listen_port  # Correct PID !

    # We don't detect this at present:
    # Could be detected because we fetch the PID directly via HTTP, but this
    # is dumb because kolibri could be running in a worker pool with different
    # PID from the PID file..
    # raise NotRunning(STATUS_UNKNOWN_INSTANCE)
    # This would be the fallback when we know it's not running, but we can't
    # give a proper reason...
    # raise NotRunning(STATUS_UNKNOW)

def get_urls(listen_port=None):
    """
    :param listen_port: if set, will not try to determine the listen port from
                        other running instances.
    """
    try:
        if listen_port:
            port = listen_port
        else:
            __, __, port = get_status()
        urls = []
        interfaces = ifcfg.interfaces()
        for interface in filter(lambda i: i['inet'], interfaces.values()):
            urls.append("http://{}:{}/".format(interface['inet'], port))
        return STATUS_RUNNING, urls
    except NotRunning as e:
        return e.status_code, []
<|endoftext|>