#1507 Refactor init_config to accept an optional flask app param
Merged 4 years ago by qwan. Opened 4 years ago by qwan.
qwan/fm-orchestrator refactor-init-config  into  v3

@@ -0,0 +1,77 @@ 

+ # -*- coding: utf-8 -*-

+ # SPDX-License-Identifier: MIT

+ from os import environ, path

+ 

+ confdir = path.abspath(path.dirname(__file__))

+ dbdir = path.abspath(path.join(confdir, "..")) if confdir.endswith("conf") else confdir

+ 

+ 

+ class BackendConfiguration(object):

+     # How often should we resort to polling, in seconds

+     # Set to zero to disable polling

+     POLLING_INTERVAL = 600

+ 

+     # Configs for running tasks asynchronously with Celery

+     # For details of Celery configs, refer to Celery documentation:

+     # https://docs.celeryproject.org/en/latest/userguide/configuration.html

+     #

+     # Each config name consists of namespace CELERY_ and the new Celery config

+     # name converted to upper case. For example the broker url, Celery config

+     # name is broker_url, then as you can see below, the corresponding config

+     # name in MBS is CELERY_BROKER_URL.

+     CELERY_BROKER_URL = ""

+     CELERY_RESULT_BACKEND = ""

+     CELERY_IMPORTS = []

+ 

+ 

+ class TestConfiguration(BackendConfiguration):

+     LOG_LEVEL = "debug"

+     SQLALCHEMY_DATABASE_URI = environ.get(

+         "DATABASE_URI", "sqlite:///{0}".format(path.join(dbdir, "mbstest.db")))

+     DEBUG = True

+     MESSAGING = "in_memory"

+ 

+     # Global network-related values, in seconds

+     NET_TIMEOUT = 3

+     NET_RETRY_INTERVAL = 1

+     # SCM network-related values, in seconds

+     SCM_NET_TIMEOUT = 0.1

+     SCM_NET_RETRY_INTERVAL = 0.1

+ 

+     KOJI_CONFIG = "./conf/koji.conf"

+     KOJI_PROFILE = "staging"

+     SERVER_NAME = "localhost"

+ 

+     KOJI_REPOSITORY_URL = "https://kojipkgs.stg.fedoraproject.org/repos"

+     SCMURLS = ["https://src.stg.fedoraproject.org/modules/"]

+ 

+     # Greenwave configuration

+     GREENWAVE_URL = "https://greenwave.example.local/api/v1.0/"

+     GREENWAVE_DECISION_CONTEXT = "test_dec_context"

+     GREENWAVE_SUBJECT_TYPE = "some-module"

+ 

+     STREAM_SUFFIXES = {r"^el\d+\.\d+\.\d+\.z$": 0.1}

+ 

+ 

+ class ProdConfiguration(BackendConfiguration):

+     pass

+ 

+ 

+ class LocalBuildConfiguration(BackendConfiguration):

+     CACHE_DIR = "~/modulebuild/cache"

+     LOG_LEVEL = "debug"

+     MESSAGING = "in_memory"

+ 

+     RESOLVER = "mbs"

+     RPMS_ALLOW_REPOSITORY = True

+     MODULES_ALLOW_REPOSITORY = True

+ 

+ 

+ class OfflineLocalBuildConfiguration(LocalBuildConfiguration):

+     RESOLVER = "local"

+ 

+ 

+ class DevConfiguration(LocalBuildConfiguration):

+     DEBUG = True

+     CELERY_BROKER_URL = "redis://localhost:6379/0"

+     CELERY_RESULT_BACKEND = "redis://localhost:6379/0"

file removed
-172
@@ -1,172 +0,0 @@ 

- # -*- coding: utf-8 -*-

- # SPDX-License-Identifier: MIT

- from os import environ, path

- 

- # FIXME: workaround for this moment till confdir, dbdir (installdir etc.) are

- # declared properly somewhere/somehow

- confdir = path.abspath(path.dirname(__file__))

- # use parent dir as dbdir else fallback to current dir

- dbdir = path.abspath(path.join(confdir, "..")) if confdir.endswith("conf") else confdir

- 

- 

- class BaseConfiguration(object):

-     DEBUG = False

-     # Make this random (used to generate session keys)

-     SECRET_KEY = "74d9e9f9cd40e66fc6c4c2e9987dce48df3ce98542529fd0"

-     SQLALCHEMY_DATABASE_URI = "sqlite:///{0}".format(path.join(dbdir, "module_build_service.db"))

-     SQLALCHEMY_TRACK_MODIFICATIONS = True

-     # Where we should run when running "manage.py run" directly.

-     HOST = "0.0.0.0"

-     PORT = 5000

- 

-     # Global network-related values, in seconds

-     NET_TIMEOUT = 120

-     NET_RETRY_INTERVAL = 30

- 

-     SYSTEM = "koji"

-     MESSAGING = "fedmsg"  # or amq

-     MESSAGING_TOPIC_PREFIX = ["org.fedoraproject.prod"]

-     KOJI_CONFIG = "/etc/module-build-service/koji.conf"

-     KOJI_PROFILE = "koji"

-     ARCHES = ["i686", "armv7hl", "x86_64"]

-     ALLOW_ARCH_OVERRIDE = False

-     KOJI_REPOSITORY_URL = "https://kojipkgs.fedoraproject.org/repos"

-     KOJI_TAG_PREFIXES = ["module", "scrmod"]

-     KOJI_ENABLE_CONTENT_GENERATOR = True

-     CHECK_FOR_EOL = False

-     PDC_URL = "https://pdc.fedoraproject.org/rest_api/v1"

-     PDC_INSECURE = False

-     PDC_DEVELOP = True

-     SCMURLS = ["https://src.fedoraproject.org/modules/"]

-     YAML_SUBMIT_ALLOWED = False

- 

-     # How often should we resort to polling, in seconds

-     # Set to zero to disable polling

-     POLLING_INTERVAL = 600

- 

-     # Determines how many builds that can be submitted to the builder

-     # and be in the build state at a time. Set this to 0 for no restrictions

-     NUM_CONCURRENT_BUILDS = 5

- 

-     ALLOW_CUSTOM_SCMURLS = False

- 

-     RPMS_DEFAULT_REPOSITORY = "https://src.fedoraproject.org/rpms/"

-     RPMS_ALLOW_REPOSITORY = False

-     RPMS_DEFAULT_CACHE = "http://pkgs.fedoraproject.org/repo/pkgs/"

-     RPMS_ALLOW_CACHE = False

- 

-     MODULES_DEFAULT_REPOSITORY = "https://src.fedoraproject.org/modules/"

-     MODULES_ALLOW_REPOSITORY = False

-     MODULES_ALLOW_SCRATCH = False

- 

-     ALLOWED_GROUPS = {"packager"}

- 

-     ALLOWED_GROUPS_TO_IMPORT_MODULE = set()

- 

-     # Available backends are: console and file

-     LOG_BACKEND = "console"

- 

-     # Path to log file when LOG_BACKEND is set to "file".

-     LOG_FILE = "module_build_service.log"

- 

-     # Available log levels are: debug, info, warn, error.

-     LOG_LEVEL = "info"

- 

-     # Settings for Kerberos

-     KRB_KEYTAB = None

-     KRB_PRINCIPAL = None

- 

-     # AMQ prefixed variables are required only while using 'amq' as messaging backend

-     # Addresses to listen to

-     AMQ_RECV_ADDRESSES = [

-         "amqps://messaging.mydomain.com/Consumer.m8y.VirtualTopic.eng.koji",

-         "amqps://messaging.mydomain.com/Consumer.m8y.VirtualTopic.eng.module_build_service",

-     ]

-     # Address for sending messages

-     AMQ_DEST_ADDRESS = \

-         "amqps://messaging.mydomain.com/Consumer.m8y.VirtualTopic.eng.module_build_service"

-     AMQ_CERT_FILE = "/etc/module_build_service/msg-m8y-client.crt"

-     AMQ_PRIVATE_KEY_FILE = "/etc/module_build_service/msg-m8y-client.key"

-     AMQ_TRUSTED_CERT_FILE = "/etc/module_build_service/Root-CA.crt"

- 

-     # Disable Client Authorization

-     NO_AUTH = False

- 

-     # Configs for running tasks asynchronously with Celery

-     # For details of Celery configs, refer to Celery documentation:

-     # https://docs.celeryproject.org/en/latest/userguide/configuration.html

-     #

-     # Each config name consists of namespace CELERY_ and the new Celery config

-     # name converted to upper case. For example the broker url, Celery config

-     # name is broker_url, then as you can below, the corresponding config name

-     # in MBS is CELERY_BROKER_URL.

-     CELERY_BROKER_URL = ""

-     CELERY_RESULT_BACKEND = ""

-     CELERY_IMPORTS = []

- 

- 

- class TestConfiguration(BaseConfiguration):

-     BUILD_LOGS_DIR = "/tmp"

-     BUILD_LOGS_NAME_FORMAT = "build-{id}.log"

-     LOG_BACKEND = "console"

-     LOG_LEVEL = "debug"

-     SQLALCHEMY_DATABASE_URI = environ.get(

-         "DATABASE_URI", "sqlite:///{0}".format(path.join(dbdir, "mbstest.db")))

-     DEBUG = True

-     MESSAGING = "in_memory"

-     PDC_URL = "https://pdc.fedoraproject.org/rest_api/v1"

- 

-     # Global network-related values, in seconds

-     NET_TIMEOUT = 3

-     NET_RETRY_INTERVAL = 1

-     # SCM network-related values, in seconds

-     SCM_NET_TIMEOUT = 0.1

-     SCM_NET_RETRY_INTERVAL = 0.1

- 

-     KOJI_CONFIG = "./conf/koji.conf"

-     KOJI_PROFILE = "staging"

-     SERVER_NAME = "localhost"

- 

-     KOJI_REPOSITORY_URL = "https://kojipkgs.stg.fedoraproject.org/repos"

-     SCMURLS = ["https://src.stg.fedoraproject.org/modules/"]

-     AUTH_METHOD = "oidc"

-     RESOLVER = "db"

- 

-     ALLOWED_GROUPS_TO_IMPORT_MODULE = {"mbs-import-module"}

- 

-     # Greenwave configuration

-     GREENWAVE_URL = "https://greenwave.example.local/api/v1.0/"

-     GREENWAVE_DECISION_CONTEXT = "test_dec_context"

-     GREENWAVE_SUBJECT_TYPE = "some-module"

- 

-     STREAM_SUFFIXES = {r"^el\d+\.\d+\.\d+\.z$": 0.1}

- 

- 

- class ProdConfiguration(BaseConfiguration):

-     pass

- 

- 

- class LocalBuildConfiguration(BaseConfiguration):

-     CACHE_DIR = "~/modulebuild/cache"

-     LOG_LEVEL = "debug"

-     MESSAGING = "in_memory"

- 

-     ARCH_AUTODETECT = True

-     ARCH_FALLBACK = "x86_64"

- 

-     ALLOW_CUSTOM_SCMURLS = True

-     RESOLVER = "mbs"

-     RPMS_ALLOW_REPOSITORY = True

-     MODULES_ALLOW_REPOSITORY = True

- 

- 

- class OfflineLocalBuildConfiguration(LocalBuildConfiguration):

-     RESOLVER = "local"

- 

- 

- class DevConfiguration(LocalBuildConfiguration):

-     DEBUG = True

-     LOG_BACKEND = "console"

- 

-     CELERY_BROKER_URL = "redis://localhost:6379/0"

-     CELERY_RESULT_BACKEND = "redis://localhost:6379/0"

file added
+66
@@ -0,0 +1,66 @@ 

+ # -*- coding: utf-8 -*-

+ # SPDX-License-Identifier: MIT

+ from os import environ, path

+ 

+ confdir = path.abspath(path.dirname(__file__))

+ dbdir = path.abspath(path.join(confdir, "..")) if confdir.endswith("conf") else confdir

+ 

+ 

+ class WebConfiguration(object):

+     # Where we should run when running "manage.py run" directly.

+     HOST = "0.0.0.0"

+     PORT = 5000

+ 

+ 

+ class TestConfiguration(WebConfiguration):

+     LOG_LEVEL = "debug"

+     SQLALCHEMY_DATABASE_URI = environ.get(

+         "DATABASE_URI", "sqlite:///{0}".format(path.join(dbdir, "mbstest.db")))

+     DEBUG = True

+     MESSAGING = "in_memory"

+ 

+     # Global network-related values, in seconds

+     NET_TIMEOUT = 3

+     NET_RETRY_INTERVAL = 1

+     # SCM network-related values, in seconds

+     SCM_NET_TIMEOUT = 0.1

+     SCM_NET_RETRY_INTERVAL = 0.1

+ 

+     KOJI_CONFIG = "./conf/koji.conf"

+     KOJI_PROFILE = "staging"

+     SERVER_NAME = "localhost"

+ 

+     KOJI_REPOSITORY_URL = "https://kojipkgs.stg.fedoraproject.org/repos"

+     SCMURLS = ["https://src.stg.fedoraproject.org/modules/"]

+ 

+     ALLOWED_GROUPS_TO_IMPORT_MODULE = {"mbs-import-module"}

+ 

+     # Greenwave configuration

+     GREENWAVE_URL = "https://greenwave.example.local/api/v1.0/"

+     GREENWAVE_DECISION_CONTEXT = "test_dec_context"

+     GREENWAVE_SUBJECT_TYPE = "some-module"

+ 

+     STREAM_SUFFIXES = {r"^el\d+\.\d+\.\d+\.z$": 0.1}

+ 

+ 

+ class ProdConfiguration(WebConfiguration):

+     pass

+ 

+ 

+ class LocalBuildConfiguration(WebConfiguration):

+     CACHE_DIR = "~/modulebuild/cache"

+     LOG_LEVEL = "debug"

+     MESSAGING = "in_memory"

+ 

+     ALLOW_CUSTOM_SCMURLS = True

+     RESOLVER = "mbs"

+     RPMS_ALLOW_REPOSITORY = True

+     MODULES_ALLOW_REPOSITORY = True

+ 

+ 

+ class OfflineLocalBuildConfiguration(LocalBuildConfiguration):

+     RESOLVER = "local"

+ 

+ 

+ class DevConfiguration(LocalBuildConfiguration):

+     DEBUG = True

@@ -18,7 +18,9 @@ 

    infrastructure services can pick up the work.

  """

  

+ import os.path

  import pkg_resources

+ import sys

  from celery import Celery

  from flask import Flask, has_app_context, url_for

  from flask_sqlalchemy import SQLAlchemy
@@ -34,7 +36,7 @@ 

  from module_build_service.errors import (

      ValidationError, Unauthorized, UnprocessableEntity, Conflict, NotFound,

      Forbidden, json_error)

- from module_build_service.config import init_config

+ from module_build_service.config import init_web_config, init_backend_config

  from module_build_service.proxy import ReverseProxy

  

  try:
@@ -46,7 +48,12 @@ 

  app = Flask(__name__)

  app.wsgi_app = ReverseProxy(app.wsgi_app)

  

- conf = init_config(app)

+ backend_commands = ("fedmsg-hub", "celery", "build_module_locally")

+ if any([os.path.basename(arg).startswith(backend_commands) for arg in sys.argv]):

+     # running as backend

+     conf = init_backend_config()

+ else:

+     conf = init_web_config(app)

  

  celery_app = Celery("module-build-service")

  # Convert config names specific for Celery like this:

@@ -474,7 +474,8 @@ 

      def get_session(config, login=True):

          """Create and return a koji.ClientSession object

  

-         :param config: the config object returned from :meth:`init_config`.

+         :param config: the config object returned from :meth:`init_web_config` or

+             :meth:`init_backend_config`.

          :type config: :class:`Config`

          :param bool login: whether to log into the session. To login if True

              is passed, otherwise not to log into session.

file modified
+84 -12
@@ -24,11 +24,11 @@ 

  }

  

  

- def init_config(app):

+ def init_web_config(app):

      """ Configure MBS and the Flask app

      """

      config_module = None

-     config_file = "/etc/module-build-service/config.py"

+     config_file = "/etc/module-build-service/web_config.py"

      config_section = "DevConfiguration"

  

      # automagically detect production environment:
@@ -70,9 +70,9 @@ 

      # TestConfiguration shall only be used for running tests, otherwise...

      if any(["py.test" in arg or "pytest" in arg for arg in sys.argv]):

          config_section = "TestConfiguration"

-         from conf import config

+         from conf import web_config

  

-         config_module = config

+         config_module = web_config

      # ...MODULE_BUILD_SERVICE_DEVELOPER_ENV has always the last word

      # and overrides anything previously set before!

      # Again, check Flask app (preferably) or fallback to os.environ.
@@ -81,14 +81,14 @@ 

      elif flask_app_env and "MODULE_BUILD_SERVICE_DEVELOPER_ENV" in app.request.environ:

          if app.request.environ["MODULE_BUILD_SERVICE_DEVELOPER_ENV"].lower() in true_options:

              config_section = "DevConfiguration"

-             from conf import config

+             from conf import web_config

  

-             config_module = config

+             config_module = web_config

      elif os.environ.get("MODULE_BUILD_SERVICE_DEVELOPER_ENV", "").lower() in true_options:

          config_section = "DevConfiguration"

-         from conf import config

+         from conf import web_config

  

-         config_module = config

+         config_module = web_config

      # try loading configuration from file

      if not config_module:

          try:
@@ -98,11 +98,63 @@ 

  

      # finally configure MBS and the Flask app

      config_section_obj = getattr(config_module, config_section)

-     conf = Config(config_section_obj)

+     conf = WebConfig(config_section_obj)

      app.config.from_object(config_section_obj)

      return conf

  

  

+ def init_backend_config():

+     """ Configure MBS and backend workers

+     """

+     config_module = None

+     config_file = "/etc/module-build-service/backend_config.py"

+     config_section = "DevConfiguration"

+ 

+     try:

+         with open(config_file):

+             config_section = "ProdConfiguration"

+     except Exception:

+         pass

+ 

+     # Load LocalBuildConfiguration section in case we are building modules

+     # locally.

+     if "build_module_locally" in sys.argv:

+         if "--offline" in sys.argv:

+             config_section = "OfflineLocalBuildConfiguration"

+         else:

+             config_section = "LocalBuildConfiguration"

+ 

+     # try getting config_file from os.environ

+     if "MBS_CONFIG_FILE" in os.environ:

+         config_file = os.environ["MBS_CONFIG_FILE"]

+     # try getting config_section from os.environ

+     if "MBS_CONFIG_SECTION" in os.environ:

+         config_section = os.environ["MBS_CONFIG_SECTION"]

+ 

+     true_options = ("1", "on", "true", "y", "yes")

+     # TestConfiguration shall only be used for running tests, otherwise...

+     if any(["py.test" in arg or "pytest" in arg for arg in sys.argv]):

+         config_section = "TestConfiguration"

+         from conf import backend_config

+ 

+         config_module = backend_config

+     elif os.environ.get("MODULE_BUILD_SERVICE_DEVELOPER_ENV", "").lower() in true_options:

+         config_section = "DevConfiguration"

+         from conf import backend_config

+ 

+         config_module = backend_config

+     # try loading configuration from file

+     if not config_module:

+         try:

+             config_module = imp.load_source("mbs_runtime_config", config_file)

+         except Exception:

+             raise SystemError("Configuration file {} was not found.".format(config_file))

+ 

+     config_section_obj = getattr(config_module, config_section)

+     conf = BackendConfig(config_section_obj)

+     return conf

+ 

+ 

  class Path:

      """

      Config type for paths. Expands the users home directory.
@@ -112,7 +164,7 @@ 

  

  

  class Config(object):

-     """Class representing the orchestrator configuration."""

+     """Class representing the orchestrator common configuration."""

  

      _defaults = {

          "debug": {"type": bool, "default": False, "desc": "Debug mode"},
@@ -244,7 +296,7 @@ 

          "log_level": {"type": str, "default": 0, "desc": "Log level"},

          "build_logs_dir": {

              "type": Path,

-             "default": "",

+             "default": tempfile.gettempdir(),

              "desc": "Directory to store module build logs to.",

          },

          "build_logs_name_format": {
@@ -751,7 +803,7 @@ 

  

      def _setifok_log_backend(self, s):

          if s is None:

-             self._log_backend = "console"

+             s = "console"

          elif s not in logger.supported_log_backends():

              raise ValueError("Unsupported log backend")

          self._log_backend = str(s)
@@ -908,3 +960,23 @@ 

          if i < 1:

              raise ValueError("NUM_THREADS_FOR_BUILD_SUBMISSIONS must be >= 1")

          self._num_threads_for_build_submissions = i

+ 

+ 

+ class WebConfig(Config):

+     """Class representing the orchestrator frontend web configuration."""

+     _web_defaults = {

+     }

+ 

+     def __init__(self, conf_section_obj):

+         self._defaults.update(self._web_defaults)

+         super(WebConfig, self).__init__(conf_section_obj)

+ 

+ 

+ class BackendConfig(Config):

+     """Class representing the orchestrator backend workers configuration."""

+     _backend_defaults = {

+     }

+ 

+     def __init__(self, conf_section_obj):

+         self._defaults.update(self._backend_defaults)

+         super(BackendConfig, self).__init__(conf_section_obj)

@@ -663,8 +663,8 @@ 

          bus.

  

          :param db_session: SQLAlchemy session object.

-         :param conf: MBS config object returned from function :func:`init_config`

-             which contains loaded configs.

+         :param conf: MBS config object returned from function :func:`init_web_config` or

+             :func:`init_backend_config`, which contains loaded configs.

          :type conf: :class:`Config`

          :param int state: the state value to transition to. Refer to ``BUILD_STATES``.

          :param str state_reason: optional reason of why to transform to ``state``.

file modified
+2 -1
@@ -67,7 +67,8 @@ 

              "/etc/module-build-service/",

              [

                  "conf/cacert.pem",

-                 "conf/config.py",

+                 "conf/web_config.py",

+                 "conf/backend_config.py",

                  "conf/koji.conf",

                  "conf/mock.cfg",

                  "conf/yum.conf",

file modified
+2 -2
@@ -14,7 +14,7 @@ 

  import module_build_service

  from module_build_service import db

  from module_build_service.utils import get_rpm_release, import_mmd, mmd_to_str

- from module_build_service.config import init_config

+ from module_build_service.config import init_web_config

  from module_build_service.models import (

      ModuleBuild, ModuleArch, ComponentBuild, VirtualStream,

      BUILD_STATES,
@@ -25,7 +25,7 @@ 

  

  base_dir = os.path.dirname(__file__)

  app = module_build_service.app

- conf = init_config(app)

+ conf = init_web_config(app)

  

  

  def staged_data_filename(filename):

file modified
+1 -1
@@ -8,7 +8,7 @@ 

  import module_build_service.monitor

  from module_build_service import models

  from module_build_service.db_session import db_session

- from conf.config import TestConfiguration

+ from conf.web_config import TestConfiguration

  

  from six.moves import reload_module

  from tests import app, init_data, make_module_in_db

When flask app is provided, create a config object for frontend and
configure the flask app, otherwise create a config object for backend
workers. Frontend and backend loads configuration from different
config files.

Before calling init_config, checking sys.argv to determine whether
it's running as backend, if 'fedmsg-hub-*' is present, it's running
as backend.

pretty please pagure-ci rebuild

4 years ago

Build #581 failed (commit: 505601d85fb61f3677094a22b5cd6abd2b783110).
Rebase or make new commits to rebuild.

After the change, we have three config files:
1. conf/base_config: BaseConfiguration, the common configurations are here
2. conf/web_config: we can add frontend configurations here
3. conf/workers_config: we can add workers configurations here

when 'fedmsg-hub*' or 'celery' is found in command line, we load configurations from workers_config and create the config object, otherwise we load configurations from web_config.

One thing I'm not sure is should we handle local module build differently, should we load different configuration from another file for local module build?

The c3i CI test failed due to rpmbuild failure because mbs config file is changed in this commit.

The issue this pr is trying to fix is for the migration to run backend in Celery workers, where no fedmsg-hub appears in command line. This condition should be needed to be reconsidered.

The issue this pr is trying to fix is for the migration to run backend in Celery workers, where no fedmsg-hub appears in command line. This condition should be needed to be reconsidered.

Once the migration is finished (the celery workers can run), this will be updated.

You could also just do arg.startswith(("fedmsg-hub", "celery")).

rebased onto 7a1801759fcc495eb10dff482445511a301e15f5

4 years ago

You could also just do arg.startswith(("fedmsg-hub", "celery")).

ok, it doesn't hurt to have it ready first :)

Patch is updated with one line change per comment.

This looks good to me, let's wait for @mprahl.

Can we decouple init_config with flask here? I'm thinking init_config should be told from where and which config section to load config. For the former, init_config could accept an argument for values web and backend. For the latter, init_config just checks the supported environment variables from the 'environ', which should be the os.environ, or the merge result of app.request.environ and os.environ if loading for web, and finally passed into function.

On the other hand, does Flask app.request.environ contain all values from os.environ when start frontend locally? If yes, init_config can be simplified too much.

Build #583 failed (commit: 7a1801759fcc495eb10dff482445511a301e15f5).
Rebase or make new commits to rebuild.

Can we decouple init_config with flask here?

We can't simply remove flask_app here, beside of creating the config object, we also configure flask_app (while running as frontend) with:

flask_app.config.from_object(config_section_obj)

A doable way is to rename init_config to load_config and make the function just load config from specific config file. Then, in module_build_service/__init__.py, initialize the global variable conf and config flask app app.config.from_object(config_section_obj). Like this:

config_section_obj = load_config(...)
conf = Config(config_section_obj)
app.config.from_object(config_section_obj)

A doable way is to rename init_config to load_config and make the function just load config from specific config file. Then, in module_build_service/init.py, initialize the global variable conf and config flask app app.config.from_object(config_section_obj).

I don't see any benefit of this and I'd prefer not to import any other internal modules (config to have Config here) in __init__.py as possible as we can.

I think the BaseConfiguration class should be removed. All default values that are MBS specific should be present in module_build_service/config.py. If they are not, that is a bug. Any Flask configuration should be in the new WebConfiguration class. The only exception I can think of is sqlalchemy_database_uri, which needs to be in both the WebConfiguration and WorkersConfiguration class.

I know it's confusing, but we started out with just configuration classes, and then module_build_service/config.py was introduced and we never cleaned this up.

Could you set this as a default value in module_build_service/config.py instead? I'm not sure why it's not set there.

Could you please remove this? This is the default in module_build_service/config.py.

Could you please remove this? This is the default in module_build_service/config.py.

Could you please remove this? This is the default in module_build_service/config.py.

Could you please remove this? This is the default in module_build_service/config.py.

Could you please remove this? This is the default in module_build_service/config.py.

Could you please remove this? This is the default in module_build_service/config.py.

@qwan I started identifying the configuration values that are just setting the default value from module_build_service/config.py, but there are too many to comment on each one. Could you please clean that up?

@qwan this is optional, but I think the cleanest approach would be to use the Flask application factory methodology as described in https://flask.palletsprojects.com/en/1.1.x/patterns/appfactories/.
Our other projects such as Cachito, Greenwave, and WaiverDB all use this approach.

Then in the create_app function, it would configure Flask from the configuration created by init_config.

Then MBS could use a global configuration object for the time being for both the frontend and backend, but the frontend only configures Flask and the backend would configure Celery.

Optional: You could use class inheritance where you would have Config as the base class and then you could have a class called WebConfig (APIConfig works too) and BackendConfig (WorkerConfig works too) that both inherit from the Config class. Then the constructors for the subclasses would handle things differently. It might be cleaner over having a bunch of if statements.

rebased onto 15b5bc5c8a68ae53b53102642aeac47bd948486d

4 years ago

Build #595 failed (commit: 15b5bc5c8a68ae53b53102642aeac47bd948486d).
Rebase or make new commits to rebuild.

Updated patch per comments:
1. Removed BaseConfiguration and base_config.py file.
2. Set default value of BUILD_LOGS_DIR in config.py.
3. Removed all settings with defaults values in config.py from config files.

Hi @mprahl , for the 2 optional changes you suggested, they're not addressed in this update:
1. Creating configuration with init_config and then create_app: currently init_config has some code of checking flask app envs to determine the config file and config section, we need pass flask app to init_config if we still need that part of code, and I'm not sure whether it's ok to remove them. I can open another PR to do it (the factory methodology) if we can drop that part of code in init_config.
2. Split up Config: to split the Config, we need to check all the 101 options to know which are frontend only, backend only or shared by both, it need some time to figure out, I've created an internal issue (FACTORY-5533 ) for it.

rebased onto 932f6c0737f991f5dd4385df038ebb67c9898fee

4 years ago

Build #596 failed (commit: 932f6c0737f991f5dd4385df038ebb67c9898fee).
Rebase or make new commits to rebuild.

Rebased on top of latest v3 branch.
Please ignore the ci failures:
1. 'c3i-jenkins' failed due to rpmbuild error because we changed the config file.
2. 'jenkins' failed because it's trying to merge the patch against master rather than v3.

Tests have been ran with both py2 and py3 locally, and passed.

Updated patch per comments:
1. Removed BaseConfiguration and base_config.py file.
2. Set default value of BUILD_LOGS_DIR in config.py.
3. Removed all settings with defaults values in config.py from config files.
Hi @mprahl , for the 2 optional changes you suggested, they're not addressed in this update:
1. Creating configuration with init_config and then create_app: currently init_config has some code of checking flask app envs to determine the config file and config section, we need pass flask app to init_config if we still need that part of code, and I'm not sure whether it's ok to remove them. I can open another PR to do it (the factory methodology) if we can drop that part of code in init_config.
2. Split up Config: to split the Config, we need to check all the 101 options to know which are frontend only, backend only or shared by both, it need some time to figure out, I've created an internal issue (FACTORY-5533 ) for it.

@qwan I was just suggesting to have two classes but both inherit the current Config class. This has two benefits. The first being that we can have separate constructors instead of an init_config method with if conditions for both as mentioned previously. The second is that any new configuration values that are specific to the frontend or backend can go in the correct configuration class. In particular, some of the Celery configuration is going to be worker specific.

Actually splitting that configuration up can be left in the backlog. I might take it up if I have spare cycles this sprint.

rebased onto b811654579d580551827770d99ae809f53bca91e

4 years ago

@qwan I was just suggesting to have two classes but both inherit the current Config class.

Hi @mprahl , I see, thanks for the comments, I updated the commit to address that, could you take a look again? unittests passed locally with both py2 and py3.

Build #601 failed (commit: b811654579d580551827770d99ae809f53bca91e).
Rebase or make new commits to rebuild.

@qwan what do you think about making this a classmethod so that it can be overridden and has a common class API? BackendConfig.init_config If you can, some of the base logic could be moved to the base class to avoid code duplication (in its own init_config method or helper methods that the derived classes can call), but I think some duplication will be necessary.

@qwan what do you think about making this a classmethod so that it can be overridden and has a common class API?

Hi @mprahl , can we do that when we have the chance to split MBS to sub components/packages (there is already an internal issue to track that)? As we have frontend config checking flask app environs, but backend doesn't, I feel like constructing some common APIs for both frontend and backend doesn't help much on making the code cleaner.
Or if we can remove the code of checking flask app environs for frontend config, it would be much easier, I don't know the background and importance of having such code, so didn't make such change.

You also need to check if this is a local build by checking if build_module_locally is in sys.argv.

Optional: It'd be nice to make this an instance variable instead of a class variable. Then you could use normal inheritance in WebConfig and BackendConfig instead of copying the static variable to the subclasses.

@qwan this is good enough to merge after addressing my comments.

rebased onto f47c1342ee6747203e61f62f4b93e9e146538463

4 years ago

You also need to check if this is a local build by checking if build_module_locally is in sys.argv.

Done

Optional: It'd be nice to make this an instance variable instead of a class variable. Then you could use normal inheritance in WebConfig and BackendConfig instead of copying the static variable to the subclasses.

Done, added __init__ in subclasses to update _defaults rather than copy the default settings as class variable.

Build #609 failed (commit: f47c1342ee6747203e61f62f4b93e9e146538463).
Rebase or make new commits to rebuild.

rebased onto a207d97

4 years ago

Pull-Request has been merged by qwan

4 years ago