Revision 32401481

b/Changelog
31 31
------------
32 32

  
33 33
* Integrate Pithos tests in continuous integration.
34
* New settings framework. Settings are annotated with descriptions,
35
default values, and callbacks for configuration and validation.
36
The framework can distinguish between settings configured in files
37
and settings left default. A management command allows listing
38
settings with filtering and various display modes.
34 39

  
35 40
.. _Changelog-0.14:
36 41

  
b/snf-astakos-app/astakos/settings/__init__.py
1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33
#
34

  
35
"""
36
Django settings metadata. To be used in setup.py snf-webproject entry points.
37
"""
38

  
39
installed_apps = [
40
    {'before': 'django.contrib.admin',
41
     'insert': 'astakos.im', },
42
    'django.contrib.auth',
43
    'django.contrib.contenttypes',
44
    'django.contrib.sessions',
45
    'django.contrib.messages',
46
    'django_tables2',
47
    'astakos.quotaholder_app',
48
    'synnefo_branding',
49
    # 'debug_toolbar',
50
]
51

  
52
context_processors = [
53
    'django.contrib.auth.context_processors.auth',
54
    'django.core.context_processors.media',
55
    'django.core.context_processors.request',
56
    'django.core.context_processors.csrf',
57
    'astakos.im.context_processors.media',
58
    'astakos.im.context_processors.im_modules',
59
    'astakos.im.context_processors.auth_providers',
60
    'astakos.im.context_processors.next',
61
    'astakos.im.context_processors.code',
62
    'astakos.im.context_processors.invitations',
63
    'astakos.im.context_processors.menu',
64
    'astakos.im.context_processors.custom_messages',
65
    'astakos.im.context_processors.last_login_method',
66
    'astakos.im.context_processors.membership_policies',
67
    'synnefo.lib.context_processors.cloudbar'
68
]
69

  
70
middlware_classes = [
71
    'django.contrib.sessions.middleware.SessionMiddleware',
72
    'django.contrib.auth.middleware.AuthenticationMiddleware',
73
    'synnefo.lib.middleware.LoggingConfigMiddleware',
74
    'synnefo.lib.middleware.SecureMiddleware',
75
    'django.middleware.csrf.CsrfViewMiddleware',
76
    'django.contrib.messages.middleware.MessageMiddleware',
77
    # 'debug_toolbar.middleware.DebugToolbarMiddleware',
78
]
79

  
80
static_files = {'astakos.im': ''}
b/snf-astakos-app/astakos/settings/default/__init__.py
1
from synnefo.settings.setup import Setting, Mandatory, Default, Constant
2

  
3
ASTAKOS_COOKIE_DOMAIN = Mandatory(
4
    example_value=".example.synnefo.org",
5
    description=(
6
        "The domain at which the astakos authentication cookie will be "
7
        "published. Warning: all websites under this domain can access the "
8
        "cookie and its secret authorization data."),
9
)
10

  
11
ASTAKOS_COOKIE_NAME = Constant(
12
    default_value='_pithos2_a',
13
    description="The astakos cookie name.",
14
)
15

  
16
ASTAKOS_COOKIE_SECURE = Constant(
17
    default_value=True,
18
    description=("Whether to require an encrypted connection "
19
                 "to transmit the cookie."),
20
)
21

  
22
# The following settings will replace the default django settings
23
AUTHENTICATION_BACKENDS = (
24
    'astakos.im.auth_backends.EmailBackend',
25
    'astakos.im.auth_backends.TokenBackend')
26

  
27
CUSTOM_USER_MODEL = 'astakos.im.AstakosUser'
28

  
29
#SOUTH_TESTS_MIGRATE = False
30

  
31
BROKER_URL = ''
32

  
33
# INTERNAL_IPS = ('127.0.0.1',)
/dev/null
1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33
#
34

  
35
"""
36
Django settings metadata. To be used in setup.py snf-webproject entry points.
37
"""
38

  
39
installed_apps = [
40
    {'before': 'django.contrib.admin',
41
     'insert': 'astakos.im', },
42
    'django.contrib.auth',
43
    'django.contrib.contenttypes',
44
    'django.contrib.sessions',
45
    'django.contrib.messages',
46
    'django_tables2',
47
    'astakos.quotaholder_app',
48
    'synnefo_branding',
49
#    'debug_toolbar',
50
]
51

  
52
context_processors = [
53
    'django.contrib.auth.context_processors.auth',
54
    'django.core.context_processors.media',
55
    'django.core.context_processors.request',
56
    'django.core.context_processors.csrf',
57
    'astakos.im.context_processors.media',
58
    'astakos.im.context_processors.im_modules',
59
    'astakos.im.context_processors.auth_providers',
60
    'astakos.im.context_processors.next',
61
    'astakos.im.context_processors.code',
62
    'astakos.im.context_processors.invitations',
63
    'astakos.im.context_processors.menu',
64
    'astakos.im.context_processors.custom_messages',
65
    'astakos.im.context_processors.last_login_method',
66
    'astakos.im.context_processors.membership_policies',
67
    'synnefo.lib.context_processors.cloudbar'
68
]
69

  
70
middlware_classes = [
71
    'django.contrib.sessions.middleware.SessionMiddleware',
72
    'django.contrib.auth.middleware.AuthenticationMiddleware',
73
    'synnefo.lib.middleware.LoggingConfigMiddleware',
74
    'synnefo.lib.middleware.SecureMiddleware',
75
    'django.middleware.csrf.CsrfViewMiddleware',
76
    'django.contrib.messages.middleware.MessageMiddleware',
77
    # 'debug_toolbar.middleware.DebugToolbarMiddleware',
78
]
79

  
80
static_files = {'astakos.im': ''}
81

  
82
# The following settings will replace the default django settings
83
AUTHENTICATION_BACKENDS = (
84
    'astakos.im.auth_backends.EmailBackend',
85
    'astakos.im.auth_backends.TokenBackend')
86

  
87
CUSTOM_USER_MODEL = 'astakos.im.AstakosUser'
88

  
89
#SOUTH_TESTS_MIGRATE = False
90

  
91
BROKER_URL = ''
92

  
93
# INTERNAL_IPS = ('127.0.0.1',)
b/snf-astakos-app/setup.py
205 205
    scripts=['astakos/scripts/snf-component-register'],
206 206
    entry_points={
207 207
        'synnefo': [
208
            'default_settings = astakos.synnefo_settings',
209
            'web_apps = astakos.synnefo_settings:installed_apps',
210
            'web_middleware = astakos.synnefo_settings:middlware_classes',
211
            'web_context_processors = astakos.synnefo_settings:context_processors',
208
            'default_settings = astakos.settings.default',
209
            'web_apps = astakos.settings:installed_apps',
210
            'web_middleware = astakos.settings:middlware_classes',
211
            'web_context_processors = astakos.settings:context_processors',
212 212
            'urls = astakos.urls:urlpatterns',
213
            'web_static = astakos.synnefo_settings:static_files'
213
            'web_static = astakos.settings:static_files'
214 214
        ],
215 215
        'console_scripts': [
216 216
            'astakos-migrate-0.14 = astakos.scripts.upgrade.migrate_014:main',
b/snf-common/synnefo/settings/__init__.py
32 32
# or implied, of GRNET S.A.
33 33

  
34 34
import os
35
import sys
36

  
37
from synnefo.util.entry_points import extend_settings
35
from sys import modules, stderr
36
_module = modules[__name__]
38 37

  
39 38
# set synnefo package __file__ to fix django related bug
40 39
import synnefo
41 40
synnefo.__file__ = os.path.join(synnefo.__path__[0], '__init__.py')
42 41

  
43
# import default settings
44
from synnefo.settings.default import *
45
from .setup import Example, Default
42
from .setup import Setting
43
synnefo_settings = {}
44
# insert global default synnefo settings
45
from .default import *
46
for name in dir(_module):
47
    if not Setting.is_valid_setting_name(name):
48
        continue
49
    synnefo_settings[name] = getattr(_module, name)
46 50

  
47 51
# autodetect default settings provided by synnefo applications
48
extend_settings(__name__, 'synnefo')
49

  
50
SYNNEFO_SETTINGS_SETUP_MODULES = ['synnefo.settings.setup.services']
52
from synnefo.util.entry_points import get_entry_points
53
for e in get_entry_points('synnefo', 'default_settings'):
54
    m = e.load()
55
    print "loading", m
56
    for name in dir(m):
57
        if name.startswith('__'):
58
            continue
59
        synnefo_settings[name] = getattr(m, name)
51 60

  
52
from .setup import preproc_settings
53
preproc_settings(sys.modules[__name__])
54
del preproc_settings
61
# set strict to True to require annotation of all settings
62
Setting.initialize_settings(synnefo_settings, strict=False)
63
_module.__dict__.update(Setting.Catalogs['defaults'])
55 64

  
56 65
# extend default settings with settings provided within *.conf user files
57 66
# located in directory specified in the SYNNEFO_SETTINGS_DIR
58 67
# environment variable
68
import re
69
system_conf_re = re.compile('^([0-9]\+-)?system.conf$')
70

  
59 71
SYNNEFO_SETTINGS_DIR = os.environ.get('SYNNEFO_SETTINGS_DIR', "/etc/synnefo/")
60 72
if os.path.exists(SYNNEFO_SETTINGS_DIR):
61 73
    try:
......
64 76
        conffiles = [f for f in entries if os.path.isfile(f) and
65 77
                     f.endswith(".conf")]
66 78
    except Exception as e:
67
        print >> sys.stderr, "Failed to list *.conf files under %s" % \
79
        print >> stderr, "Failed to list *.conf files under %s" % \
68 80
            SYNNEFO_SETTINGS_DIR
69 81
        raise SystemExit(1)
70 82
    conffiles.sort()
71 83
    for f in conffiles:
84
        if system_conf_re.match(f):
85
            allow_known = False
86
            allow_unknown = True
87
        else:
88
            allow_known = True
89
            allow_unknown = False
90

  
91
        # FIXME: Hack until all settings have been annotated properly
92
        allow_unknown = True
93
        allow_override = True
94

  
72 95
        try:
73
            execfile(os.path.abspath(f))
96
            path = os.path.abspath(f)
97
            old_settings = Setting.Catalogs['defaults']
98
            new_settings = Setting.load_settings_from_file(path, old_settings)
99

  
100
            Setting.load_configuration(new_settings,
101
                                       source=path,
102
                                       allow_known=allow_known,
103
                                       allow_unknown=allow_unknown,
104
                                       allow_override=allow_override)
74 105
        except Exception as e:
75
            print >> sys.stderr, "Failed to read settings file: %s [%r]" % \
76
                (os.path.abspath(f), e)
106
            print >> stderr, "Failed to read settings file: %s [%r]" % \
107
                (path, e)
108
            raise
77 109
            raise SystemExit(1)
78 110

  
111
Setting.configure_settings()
112
_module.__dict__.update(Setting.Catalogs['runtime'])
79 113

  
80
import sys
81
from .setup import postproc_settings
82
postproc_settings(sys.modules[__name__])
83
del postproc_settings
84

  
85
for _module_path in SYNNEFO_SETTINGS_SETUP_MODULES:
86
    _temp = __import__(_module_path, globals(), locals(),
87
                       ['setup_settings'], 0)
88
    _temp.setup_settings(sys.modules[__name__])
89

  
90
del _temp
91
del _module_path
114
# cleanup module namespace
115
for _name in dir(_module):
116
    if _name.startswith('_') or _name.isupper():
117
        continue
118
    delattr(_module, _name)
119
del _name
120
del _module
b/snf-common/synnefo/settings/default/__init__.py
31 31
# interpreted as representing official policies, either expressed
32 32
# or implied, of GRNET S.A.
33 33

  
34
from synnefo.settings.default.admins import *
34
from .services import *
35
from .admins import *
b/snf-common/synnefo/settings/default/services.py
1
# Copyright 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

  
34
from synnefo.util.entry_points import extend_dict_from_entry_point
35
from synnefo.util.keypath import customize_from_items
36
from synnefo.lib.services import fill_endpoints
37
from synnefo.lib import parse_base_url
38

  
39
from synnefo.settings.setup import Setting, Mandatory, Auto, Default
40

  
41
CUSTOMIZE_SERVICES = Default(
42
    default_value=(),
43
    example_value={'cyclades_ui.prefix': 'view',
44
                   'astakos_ui.prefix': 'view'},
45
    export=0,
46
    #dependencies=('SYNNEFO_SERVICES',)  #uncomment this to test cycle
47
    description=("A list of key-path and value pairs that would be applied to "
48
                 "the services registry after its automatic initialization."),
49
)
50

  
51
services = {}
52
extend_dict_from_entry_point(services, 'synnefo', 'services')
53

  
54
from sys import modules
55
module = modules[__name__]
56

  
57
components = {}
58
for service_name, service in services.items():
59
    component_name = service['component']
60
    if component_name not in components:
61
        components[component_name] = {}
62
    components[component_name][service_name] = service
63

  
64
base_url_names = []
65
for component_name in components:
66
    name_upper = component_name.upper()
67

  
68
    base_url_name = name_upper + '_BASE_URL'
69
    base_url_names.append(base_url_name)
70
    base_url_example_value = "https://{comp}.example.synnefo.org/{comp}/"
71
    base_url_example_value = base_url_example_value.format(comp=component_name)
72
    base_url_description = (
73
        "The HTTP URL prefix (scheme, host, and path) beneath which all "
74
        "services for component {comp} reside.")
75
    base_url_description = base_url_description.format(comp=component_name)
76
    base_url_setting = Mandatory(
77
        example_value=base_url_example_value,
78
        description=base_url_description,
79
    )
80
    setattr(module, base_url_name, base_url_setting)
81

  
82
    def mk_auto_configure_base_host(base_url_name):
83
        def auto_configure_base_host(setting, value, deps):
84
            Setting.enforce_not_configurable(setting, value)
85
            base_url = deps[base_url_name]
86
            base_host, base_path = parse_base_url(base_url)
87
            return base_host
88
        return auto_configure_base_host
89

  
90
    base_host_name = name_upper + '_BASE_HOST'
91
    base_host_description = "The host part of {base}. Cannot be configured."
92
    base_host_description = base_host_description.format(base=base_url_name)
93
    base_host_setting = Auto(
94
        configure_callback=mk_auto_configure_base_host(base_url_name),
95
        export=0,
96
        dependencies=(base_url_name,),
97
        description=base_host_description,
98
    )
99
    setattr(module, base_host_name, base_host_setting)
100

  
101
    def mk_auto_configure_base_path(base_url_name):
102
        def auto_configure_base_path(setting, value, deps):
103
            Setting.enforce_not_configurable(setting, value)
104
            base_url = deps[base_url_name]
105
            base_host, base_path = parse_base_url(base_url)
106
            return base_path
107
        return auto_configure_base_path
108

  
109
    base_path_name = name_upper + '_BASE_PATH'
110
    base_path_description = "The path part of {base}. Cannot be configured."
111
    base_path_description = base_path_description.format(base=base_url_name)
112
    base_path_setting = Auto(
113
        configure_callback=mk_auto_configure_base_path(base_url_name),
114
        export=0,
115
        dependencies=(base_url_name,),
116
        description=base_path_description,
117
    )
118
    setattr(module, base_path_name, base_path_setting)
119

  
120

  
121
SYNNEFO_COMPONENTS = Auto(
122
    configure_callback=Setting.enforce_not_configurable,
123
    export=0,
124
    default_value=components,
125
    description=("A list with the names of all synnefo components currently "
126
                 "installed. Initialized from SYNNEFO_SERVICES. "
127
                 "It is dynamically generated and cannot be configured."),
128
    dependencies=('SYNNEFO_SERVICES',),
129
)
130

  
131

  
132
def auto_configure_services(setting, value, deps):
133
    Setting.enforce_not_configurable(setting, value)
134
    services = setting.default_value
135
    customization = deps['CUSTOMIZE_SERVICES']
136
    if isinstance(customization, dict):
137
        customization = customization.items()
138
    customize_from_items(services, customization)
139
    for service_name, service in services.iteritems():
140
        component_name = service['component']
141
        base_url_name = component_name.upper() + '_BASE_URL'
142
        base_url = deps[base_url_name]
143
        fill_endpoints(service, base_url)
144
    return services
145

  
146

  
147
SYNNEFO_SERVICES = Auto(
148
    configure_callback=auto_configure_services,
149
    export=0,
150
    default_value=services,
151
    dependencies=('CUSTOMIZE_SERVICES',) + tuple(base_url_names),
152
    description=("An auto-generated registry of all services provided by all "
153
                 "currently installed Synnefo components. "
154
                 "It is dynamically generated and cannot be configured. "
155
                 "For service customization use CUSTOMIZE_SERVICES."),
156
)
b/snf-common/synnefo/settings/setup.py
1
# Copyright 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

  
34
from collections import defaultdict
35
from pprint import pformat
36
from textwrap import wrap
37
from itertools import chain
38
from os import environ
39

  
40

  
41
class Setting(object):
42
    """Setting is the parent class of all setting annotations.
43

  
44
    Setting.initialize_settings() will register a dictionary
45
    of setting names to Setting instances, process all
46
    annotations.
47

  
48
    Setting.load_configuration() will load user-specific
49
    configurations to the defaults.
50
    A setting set in this stage will be flagged 'configured'.
51
    Those that are not set in this stage will be flagged 'default'.
52

  
53
    Setting.configure_settings() will post-process all settings,
54
    maintaining the various registries and calling configuration
55
    callbacks for auto-generation or validation.
56

  
57
    A setting has the following attributes:
58

  
59
    ** CONFIGURATION ATTRIBUTES **
60

  
61
    default_value:
62
        The default value to be assigned if not given any by the
63
        administrator in the config files. To omit a default value
64
        assign Setting.NoValue to it.
65

  
66
    example_value:
67
        A value to serve as an informative guide for those who
68
        want to configure the setting. The default_value might not
69
        be that informative.
70

  
71
    description:
72
        Setting description for administrators and developers.
73

  
74
    category:
75
        An all-lowercase category name for grouping settings together.
76

  
77
    dependencies:
78
        A list of setting names and categories whose values will be
79
        given as input to configure_callback.
80

  
81
    configure_callback:
82
        A function that accepts three arguments, a Setting instance,
83
        the corresponding setting value given by the configuration
84
        files, and a dictionary with all setting dependencies declared.
85
        If the setting value has not be set by a config file,
86
        then its value (second argument) will be Setting.NoValue.
87

  
88
        If no value was provided by a config file, the callback must
89
        return the final setting value which will be assigned to it.
90
        If a value was provided, the callback must return
91
        Setting.NoValue to acknowledge it.
92

  
93
    export:
94
        If export is false then the setting will be marked not to be
95
        advertised in user-friendly lists or files.
96

  
97
    ** STATE ATTRIBUTES **
98

  
99
    setting_name:
100
        This is the name this setting annotation was given to.
101
        Initialized as None. Settings.initialize_settings will set it.
102

  
103
    configured_value:
104
        This is the value given by a configuration file.
105
        If it does not exist, then the setting was not set by
106
        the administrator.
107

  
108
        Initialized as Setting.NoValue.
109
        Settings.load_configuration will set it.
110

  
111
    configured_source:
112
        This is the source (e.g. configuration file path) where the
113
        setting was configured. It is None if the setting has not
114
        been configured.
115

  
116
    configured_depth:
117
        The depth of the setting in the dependency tree (forest).
118

  
119
    runtime_value:
120
        This is the final setting value after all configuration
121
        processing. Its existence indicates that all processing
122
        for this setting has been completed.
123

  
124
        Initialized as Setting.NoValue.
125
        Settings.configure_one_setting will set it upon completion.
126

  
127
    serial:
128
        The setting's serial index in the 'registry' catalog.
129
        Represents the chronological order of execution of its annotation.
130

  
131
    dependents:
132
        A list of the names of the settings that depend on this one.
133

  
134
    fail_exception:
135
        If the configuration of a setting has failed, this holds
136
        the exception raised, marking it as failed to prevent
137
        further attempts, and to be able to re-raise the error.
138
        Initialized as None.
139

  
140
    Subclasses may expose any subset of the above, as well as additional
141
    attributes of their own.
142

  
143
    Setting will construct certain setting catalogs during runtime
144
    initialization, later accessible via a 'Catalogs' class attribute
145
    dictionary:
146

  
147
        catalog = Setting.Catalogs['catalog_name']
148

  
149
    The catalogs are:
150

  
151
    1. Catalog 'settings'
152

  
153
       A catalog of all setting annotations collected at initialization.
154
       This is useful to access setting annotations at runtime,
155
       for example a setting's default value:
156

  
157
       settings = Setting.Catalogs['settings']
158
       settings['SOCKET_CONNECT_RETRIES'].default_value
159

  
160
    2. Catalog 'types'
161

  
162
       A catalog of the different types of settings.
163
       At each Setting instantiation, the (sub)class attribute
164
       'setting_type' will be used as the setting type name in the
165
       catalog.  Each entry in the catalog is a dictionary of setting
166
       names and setting annotation instances. For example:
167
       
168
       setting_types = Setting.Catalogs['types']
169
       setting_types['mandatory']['SETTING_NAME'] == setting_instance
170

  
171
    3. Catalog 'categories'
172

  
173
       A catalog of the different setting categories.
174
       At each Setting instantiation, the instance attribute 'category'
175
       will be used to group settings in categories. For example:
176

  
177
       categories = Setting.Catalogs['categories']
178
       categories['django']['SETTING_NAME'] == setting_instance
179

  
180
    4. Catalog 'defaults'
181

  
182
       A catalog of all settings that have been initialized and their
183
       default, pre-configuration values. This catalog is useful as
184
       input to the configuration from files.
185

  
186
    5. Catalog 'configured'
187

  
188
       A catalog of all settings that were configured by the
189
       administrator in the configuration files. Very relevant
190
       to the administrator as it effectively represents the
191
       deployment-specific configuration, without noise from
192
       all settings left default.
193
       Each setting is registered in the catalog just before
194
       the configure_callback is called.
195
       The catalog values are final setting values, not instances.
196

  
197
       configured = Setting.Catalogs['configured']
198
       print '\n'.join(("%s = %s" % it) for it in configured.items())
199

  
200
    5. Catalog 'runtime'
201

  
202
        A catalog of all finalized settings and their runtime values.
203
        This is output of the setting configuration process and
204
        where setting values must be read from at runtime.
205

  
206
    """
207

  
208
    class SettingsError(Exception):
209
        pass
210

  
211
    NoValue = type('NoValue', (), {})
212

  
213
    setting_type = 'setting'
214
    _serial = 0
215

  
216
    Catalogs = {
217
        'registry': {},
218
        'settings': {},
219
        'types': defaultdict(dict),
220
        'categories': defaultdict(dict),
221
        'defaults': {},
222
        'configured': {},
223
        'runtime': {},
224
    }
225

  
226
    default_value = None
227
    example_value = None
228
    description = 'This setting is missing documentation'
229
    category = 'misc'
230
    dependencies = ()
231
    dependents = ()
232
    export = True
233

  
234
    serial = None
235
    configured_value = NoValue
236
    configured_source = None
237
    configured_depth = 0
238
    runtime_value = NoValue
239
    fail_exception = None
240

  
241
    def __repr__(self):
242
        flags = []
243
        if self.configured_value is not Setting.NoValue:
244
            flags.append("configured")
245
            value = self.configured_value
246
        else:
247
            flags.append("default")
248
            value = self.default_value
249

  
250
        if self.runtime_value is not Setting.NoValue:
251
            flags.append("finalized")
252

  
253
        if self.fail_exception is not None:
254
            flags.append("failed({0})".format(self.fail_exception))
255
        r = "<{setting_type}[{flags}]: {value}>" 
256
        r = r.format(setting_type=self.setting_type,
257
                     value=repr(value),
258
                     flags=','.join(flags))
259
        return r
260

  
261
    __str__ = __repr__
262

  
263
    def present_as_comment(self):
264
        header = "# {name}: type {type}, category '{categ}'"
265
        header = header.format(name=self.setting_name,
266
                               type=self.setting_type.upper(),
267
                               categ=self.category)
268
        header = [header]
269

  
270
        if self.dependencies:
271
            header += ["# Depends on: "]
272
            header += ["#     " + d for d in sorted(self.dependencies)]
273

  
274
        description = wrap(self.description, 70)
275
        description = [("# " + s) for s in description]
276

  
277
        example_value = self.example_value
278
        default_value = self.default_value
279
        if example_value != default_value:
280
            example = "Example value: {0}"
281
            example = example.format(pformat(example_value)).split('\n')
282
            description += ["# "]
283
            description += [("# " + s) for s in example]
284

  
285
        assignment = "{name} = {value}"
286
        assignment = assignment.format(name=self.setting_name,
287
                                       value=pformat(default_value))
288
        assignment = [("#" + s) for s in assignment.split('\n')]
289

  
290
        return '\n'.join(chain(header, ['#'],
291
                               description, ['#'],
292
                               assignment))
293

  
294
    @staticmethod
295
    def configure_callback(setting, value, dependencies):
296
        if value is Setting.NoValue:
297
            return setting.default_value
298
        else:
299
            # by default, acknowledge the configured value
300
            # and allow it to be used.
301
            return Setting.NoValue
302

  
303
    def validate(self):
304
        """Example setting validate method"""
305

  
306
        NoValue = Setting.NoValue
307
        setting_name = self.setting_name
308
        if self is not Setting.Catalogs['settings'][setting_name]:
309
            raise AssertionError()
310

  
311
        runtime_value = self.runtime_value
312
        if runtime_value is NoValue:
313
            raise AssertionError()
314

  
315
        configured_value = self.configured_value
316
        if configured_value not in (NoValue, runtime_value):
317
            raise AssertionError()
318

  
319
    def __init__(self, **kwargs):
320

  
321
        attr_names = ['default_value', 'example_value', 'description',
322
                      'category', 'dependencies', 'configure_callback',
323
                      'export']
324

  
325
        for name in attr_names:
326
            if name in kwargs:
327
                setattr(self, name, kwargs[name])
328

  
329
        serial = Setting._serial
330
        Setting._serial = serial + 1
331
        registry = Setting.Catalogs['registry']
332
        self.serial = serial
333
        registry[serial] = self
334

  
335
    @staticmethod
336
    def is_valid_setting_name(name):
337
        return name.isupper() and not name.startswith('_')
338

  
339
    @staticmethod
340
    def get_settings_from_object(settings_object):
341
        var_list = []
342
        is_valid_setting_name = Setting.is_valid_setting_name
343
        for name in dir(settings_object):
344
            if not is_valid_setting_name(name):
345
                continue
346
            var_list.append((name, getattr(settings_object, name)))
347
        return var_list
348

  
349
    @staticmethod
350
    def initialize_settings(settings_dict, strict=False):
351
        Catalogs = Setting.Catalogs
352
        settings = Catalogs['settings']
353
        categories = Catalogs['categories']
354
        defaults = Catalogs['defaults']
355
        types = Catalogs['types']
356

  
357
        for name, value in settings_dict.iteritems():
358
            if not isinstance(value, Setting):
359
                if strict:
360
                    m = "Setting name '{name}' has non-annotated value '{value}'!"
361
                    m = m.format(name=name, value=value)
362
                    raise Setting.SettingsError(m)
363
                else:
364
                    value = Setting(default_value=value)
365

  
366
            # FIXME: duplicate annotations?
367
            #if name in settings:
368
            #    m = ("Duplicate annotation for setting '{name}': '{value}'. "
369
            #         "Original annotation: '{original}'")
370
            #    m = m.format(name=name, value=value, original=settings[name])
371
            #    raise Setting.SettingsError(m)
372
            value.setting_name = name
373
            settings[name] = value
374
            categories[value.category][name] = value
375
            types[value.setting_type][name] = value
376
            default_value = value.default_value
377
            defaults[name] = default_value
378

  
379
        defaults['_SETTING_CATALOGS'] = Catalogs
380

  
381
    @staticmethod
382
    def load_settings_from_file(path, settings_dict=None):
383
        if settings_dict is None:
384
            settings_dict = {}
385
        new_settings = {}
386
        execfile(path, settings_dict, new_settings)
387
        return new_settings
388

  
389
    @staticmethod
390
    def load_configuration(new_settings,
391
                           source='unknonwn',
392
                           allow_override=False,
393
                           allow_unknown=False,
394
                           allow_known=True):
395

  
396
        settings = Setting.Catalogs['settings']
397
        defaults = Setting.Catalogs['defaults']
398
        configured = Setting.Catalogs['configured']
399
        is_valid_setting_name = Setting.is_valid_setting_name
400

  
401
        for name, value in new_settings.iteritems():
402
            if not is_valid_setting_name(name):
403
                # silently ignore it?
404
                continue
405

  
406
            if name in settings:
407
                if not allow_known:
408
                    m = ("{source}: setting '{name} = {value}' not allowed to "
409
                         "be set here")
410
                    m = m.format(source=source, name=name, value=value)
411
                    raise Setting.SettingsError(m)
412
            else:
413
                if allow_unknown:
414
                    # pretend this was declared in a default settings module
415
                    desc = "Unknown setting from {source}".format(source=source)
416

  
417
                    setting = Setting(default_value=value,
418
                                      category='unknown',
419
                                      description=desc)
420
                    Setting.initialize_settings({name: setting}, strict=True)
421
                else:
422
                    m = ("{source}: unknown setting '{name} = {value}' not "
423
                         "allowed to be set here")
424
                    m = m.format(source=source, name=name, value=value)
425
                    raise Setting.SettingsError(m)
426

  
427
            if not allow_override and name in configured:
428
                m = ("{source}: new setting '{name} = {value}' "
429
                     "overrides setting '{name} = {oldval}'")
430
                m = m.format(source=source, name=name, value=value,
431
                             oldval=defaults[name])
432
                raise Setting.SettingsError(m)
433

  
434
            # setting has been accepted for configuration
435
            setting = settings[name]
436
            setting.configured_value = value
437
            setting.configured_source = source
438
            configured[name] = value
439
            defaults[name] = value
440

  
441
        return new_settings
442

  
443
    @staticmethod
444
    def configure_one_setting(setting_name, dep_stack=()):
445
        dep_stack += (setting_name,)
446
        Catalogs = Setting.Catalogs
447
        settings = Catalogs['settings']
448
        runtime = Catalogs['runtime']
449
        NoValue = Setting.NoValue
450

  
451
        if setting_name not in settings:
452
            m = "Unknown setting '{name}'"
453
            m = m.format(name=setting_name)
454
            raise Setting.SettingsError(m)
455

  
456
        setting = settings[setting_name]
457
        if setting.runtime_value is not NoValue:
458
            # already configured, nothing to do.
459
            return
460

  
461
        if setting.fail_exception is not None:
462
            # it has previously failed, re-raise the error
463
            exc = setting.fail_exception
464
            if not isinstance(exc, Exception):
465
                exc = Setting.SettingsError(str(exc))
466
            raise exc
467

  
468
        setting_value = setting.configured_value
469
        if isinstance(setting_value, Setting):
470
            m = ("Unprocessed setting annotation '{name} = {value}' "
471
                 "in setting configuration stage!")
472
            m = m.format(name=setting_name, value=setting_value)
473
            raise AssertionError(m)
474

  
475
        configure_callback = setting.configure_callback
476
        if not configure_callback:
477
            setting.runtime_value = setting_value
478
            return
479

  
480
        if not callable(configure_callback):
481
            m = ("attribute 'configure_callback' of "
482
                 "'{setting}' is not callable!")
483
            m = m.format(setting=setting)
484
            exc = Setting.SettingsError(m)
485
            setting.fail_exception = exc
486
            raise exc
487

  
488
        deps = {}
489
        for dep_name in setting.dependencies:
490
            if dep_name not in settings:
491
                m = ("Unknown dependecy setting '{dep_name}' "
492
                     "for setting '{name}'!")
493
                m = m.format(dep_name=dep_name, name=setting_name)
494
                raise Setting.SettingsError(m)
495

  
496
            if dep_name in dep_stack:
497
                m = "Settings dependency cycle detected: {stack}"
498
                m = m.format(stack=dep_stack)
499
                exc = Setting.SettingsError(m)
500
                setting.fail_exception = exc
501
                raise exc
502

  
503
            dep_setting = settings[dep_name]
504
            if dep_setting.fail_exception is not None:
505
                m = ("Cannot configure setting {name} because it depends "
506
                     "on '{dep}' which has failed to configure.")
507
                m = m.format(name=setting_name, dep=dep_name)
508
                exc = Setting.SettingsError(m)
509
                setting.fail_exception = exc
510
                raise exc
511

  
512
            if dep_setting.runtime_value is NoValue:
513
                Setting.configure_one_setting(dep_name, dep_stack)
514

  
515
            dep_value = dep_setting.runtime_value
516
            deps[dep_name] = dep_value
517

  
518
        try:
519
            new_value = configure_callback(setting, setting_value, deps)
520
        except Setting.SettingsError as e:
521
            setting.fail_exception = e
522
            raise
523

  
524
        if new_value is not NoValue:
525
            if setting_value is not NoValue:
526
                m = ("Configure callback of setting '{name}' does not "
527
                     "acknowledge the fact that a value '{value}' was "
528
                     "provided by '{source}' and wants to assign "
529
                     "a value '{newval}' anyway!")
530
                m = m.format(name=setting_name, value=setting_value,
531
                             source=setting.configured_source,
532
                             newval=new_value)
533
                exc = Setting.SettingsError(m)
534
                setting.fail_exception = exc
535
                raise exc
536
            else:
537
                setting_value = new_value
538

  
539
        setting.runtime_value = setting_value
540
        runtime[setting_name] = setting_value
541

  
542
    @staticmethod
543
    def configure_settings(setting_names=()):
544
        settings = Setting.Catalogs['settings']
545
        if not setting_names:
546
            setting_names = settings.keys()
547

  
548
        bottom = set(settings.keys())
549
        for name, setting in settings.iteritems():
550
            dependencies = setting.dependencies
551
            if not dependencies:
552
                continue
553
            bottom.discard(name)
554
            for dep_name in setting.dependencies:
555
                dep_setting = settings[dep_name]
556
                if not dep_setting.dependents:
557
                    dep_setting.dependents = []
558
                dep_setting.dependents.append(name)
559

  
560
        depth = 1
561
        while True:
562
            dependents = []
563
            for name in bottom:
564
                setting = settings[name]
565
                setting.configured_depth = depth
566
                dependents.extend(setting.dependents)
567
            if not dependents:
568
                break
569
            bottom = dependents
570
            depth += 1
571

  
572
        failed = []
573
        for name in Setting.Catalogs['settings']:
574
            try:
575
                Setting.configure_one_setting(name)
576
            except Setting.SettingsError as e:
577
                failed.append(e)
578

  
579
        if failed:
580
            import sys
581
            sys.stderr.write('\n')
582
            sys.stderr.write('\n'.join(map(str, failed)))
583
            sys.stderr.write('\n\n')
584
            raise Setting.SettingsError("Failed to configure settings.")
585

  
586
    @staticmethod
587
    def enforce_not_configurable(setting, value, deps=None):
588
        if value is not Setting.NoValue:
589
            m = "Setting '{name}' is not configurable."
590
            m = m.format(name=setting.setting_name)
591
            raise Setting.SettingsError(m)
592
        return setting.default_value
593

  
594

  
595
class Mandatory(Setting):
596
    """Mandatory settings have to be to be configured by the
597
    administrator in the configuration files. There are no defaults,
598
    and not giving a value will raise an exception.
599

  
600
    """
601
    setting_type = 'mandatory'
602

  
603
    def __init__(self, example_value=Setting.NoValue, **kwargs):
604
        if example_value is Setting.NoValue:
605
            m = "Mandatory settings require an example_value"
606
            raise Setting.SettingsError(m)
607
        kwargs['example_value'] = example_value
608
        kwargs['export'] = True
609
        Setting.__init__(self, **kwargs)
610

  
611
    @staticmethod
612
    def configure_callback(setting, value, deps):
613
        if value is Setting.NoValue:
614
            if environ.get('SYNNEFO_RELAX_MANDATORY_SETTINGS'):
615
                return setting.example_value
616

  
617
            m = ("Setting '{name}' is mandatory. "
618
                 "Please provide a real value. "
619
                 "Example value: '{example}'")
620
            m = m.format(name=setting.setting_name,
621
                         example=setting.example_value)
622
            raise Setting.SettingsError(m)
623

  
624
        return Setting.NoValue
625

  
626

  
627
class Default(Setting):
628
    """Default settings are not mandatory.
629
    There are default values that are meant to work well, and also serve as an
630
    example if no explicit example is given.
631

  
632
    """
633
    setting_type = 'default'
634

  
635
    def __init__(self, default_value=Setting.NoValue,
636
                 description="No description", **kwargs):
637
        if default_value is Setting.NoValue:
638
            m = "Default settings require a default_value"
639
            raise Setting.SettingsError(m)
640

  
641
        kwargs['default_value'] = default_value
642
        if 'example_value' not in kwargs:
643
            kwargs['example_value'] = default_value
644
        kwargs['description'] = description
645
        Setting.__init__(self, **kwargs)
646

  
647

  
648
class Constant(Setting):
649
    """Constant settings are a like defaults, only they are not intended to be
650
    visible or configurable by the administrator.
651

  
652
    """
653
    setting_type = 'constant'
654

  
655
    def __init__(self, default_value=Setting.NoValue,
656
                 description="No description", **kwargs):
657
        if default_value is Setting.NoValue:
658
            m = "Constant settings require a default_value"
659
            raise Setting.SettingsError(m)
660

  
661
        kwargs['default_value'] = default_value
662
        if 'example_value' not in kwargs:
663
            kwargs['example_value'] = default_value
664
        kwargs['export'] = False
665
        kwargs['description'] = description
666
        Setting.__init__(self, **kwargs)
667

  
668

  
669
class Auto(Setting):
670
    """Auto settings can be computed automatically.
671
    Administrators may attempt to override them and the setting
672
    may or may not accept being overriden. If override is not accepted
673
    it will result in an error, not in a silent discarding of user input.
674

  
675
    """
676
    setting_type = 'auto'
677

  
678
    def __init__(self, configure_callback=None, **kwargs):
679
        if not configure_callback:
680
            m = "Auto settings must provide a configure_callback"
681
            raise Setting.SettingsError(m)
682

  
683
        kwargs['configure_callback'] = configure_callback
684
        Setting.__init__(self, **kwargs)
685

  
686
    @staticmethod
687
    def configure_callback(setting, value, deps):
688
        raise NotImplementedError()
689

  
690

  
691
class Deprecated(object):
692
    """Deprecated settings must be removed, renamed, or otherwise fixed."""
693

  
694
    setting_type = 'deprecated'
695

  
696
    def __init__(self, rename_to=None, **kwargs):
697
        self.rename_to = rename_to
698
        kwargs['export'] = False
699
        Setting.__init__(self, **kwargs)
700

  
701
    @staticmethod
702
    def configure_callback(setting, value, deps):
703
        m = ("Setting {name} has been deprecated. "
704
             "Please consult upgrade notes and ")
705

  
706
        if setting.rename_to:
707
            m += "rename to {rename_to}."
708
        else:
709
            m += "remove it."
710

  
711
        m = m.format(name=setting.setting_name, rename_to=setting.rename_to)
712
        raise Setting.SettingsError(m)
713

  
/dev/null
1
# Copyright 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

  
34

  
35
class Example(object):
36
    """Example settings are mandatory to be configured with real values.
37
    There are no defaults, and not giving a value is a fatal error.
38

  
39
    """
40
    def __init__(self, example_value, description=None):
41
        self.example_value = example_value
42
        self.default_value = None
43
        self.description = description
44

  
45

  
46
class Default(object):
47
    """Default settings are not mandatory in nature.
48
    There are default values that are meant to work well,
49
    and also serve as an example.
50

  
51
    """
52
    def __init__(self, default_value, example=None, description=None):
53
        self.default_value = default_value
54
        if example is None:
55
            example = default_value
56
        self.example_value = example
57
        self.description = description
58

  
59

  
60
class Deprecated(object):
61
    """Deprecated settings must be removed, renamed, or otherwise fixed."""
62
    def __init__(self, rename_to=None, description=None):
63
        self.rename_to = rename_to
64
        self.description = description
65

  
66

  
67
def get_all_settings(settings):
68
    var_list = []
69
    for name in dir(settings):
70
        if not name.isupper() or name.startswith('_'):
71
            continue
72
        var_list.append((name, getattr(settings, name)))
73
    return var_list
74

  
75

  
76
def preproc_settings(settings):
77
    other = {}
78
    defaults = {}
79
    mandatory = {}
80
    deprecated = {}
81

  
82
    for name, value in get_all_settings(settings):
83
        if isinstance(value, Example):
84
            mandatory[name] = value
85
        elif isinstance(value, Default):
86
            defaults[name] = value
87
            setattr(settings, name, value.default_value)
88
        elif isinstance(value, Deprecated):
89
            deprecated[name] = value
90
        else:
91
            other[name] = value
92

  
93
    settings._OTHER = other
94
    settings._DEFAULTS = defaults
95
    settings._MANDATORY = mandatory
96
    settings._DEPRECATED = deprecated
97

  
98

  
99
def postproc_settings(settings):
100
    configured = {}
101
    defaults = settings._DEFAULTS
102
    failed = []
103
    import os
104
    relax_mandatory = bool(os.environ.get('SYNNEFO_RELAX_MANDATORY_SETTINGS'))
105

  
106
    for name, value in get_all_settings(settings):
107
        if isinstance(value, Example):
108
            if relax_mandatory:
109
                setattr(settings, name, value.example_value)
110
            else:
111
                m = ("Setting '{name}' is mandatory. "
112
                     "Please provide a real value. "
113
                     "Example value: '{example}'")
114
                m = m.format(name=name, example=value.example_value)
115
                failed.append(m)
116
        elif isinstance(value, Default):
117
            m = "unprocessed default setting in post processing"
118
            raise AssertionError(m)
119
            defaults[name] = value
120
            setattr(settings, name, value.default_value)
121
        elif isinstance(value, Deprecated):
122
            m = "Setting '{name}' has been deprecated.".format(name=name)
123
            if value.rename_to:
124
                m += " Please rename it to '{rename}'.".format(
125
                    rename=value.rename_to)
126
            if value.description:
127
                m += "details: {desc}".format(desc=value.description)
128
            failed.append(m)
129
        elif name in defaults:
130
            if defaults[name].default_value is not value:
131
                configured[name] = value
132

  
133
    settings._CONFIGURED = configured
134
    if failed:
135
        import sys
136
        sys.stderr.write('\n')
137
        sys.stderr.write('\n'.join(failed))
138
        sys.stderr.write('\n\n')
139
        raise AssertionError("Failed to read settings.")
/dev/null
1
# Copyright 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff