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 |
Also available in: Unified diff