Browse code

Add meta-config via local.conf

This defines a new local.conf file that is designed to take the place of all
of the 'pass-through'[1] configuration options that have been defined in DevStack.

* new local.conf file can contain multiple config file settings to be
merged in to existing project config files
* localrc can be embedded into local.conf and will auto-extract if
localrc does not exist
* Adds functions get_meta_section(), get_meta_section_files(),
merge_config_file() and merge_config_group()
* Adds EXTRA_OPTS, EXTRA_BAREMETAL_OPTS, Q_DHCP_EXTRA_DEFAULT_OPTS and
Q_SRV_EXTRA_DEFAULT_OPTS to the deprecated warning list at the end of stack.sh

[1] Pass-through options are those that do not configure or change DevStack's behaviour
but simply set a value in a project config file. This includes most of the EXTRA_XXX_OPTS
configuration variables.

Change-Id: I367cadc86116621e9574ac203aafdab483d810d3

Dean Troyer authored on 2013/09/14 05:05:51
Showing 6 changed files
... ...
@@ -244,3 +244,42 @@ To setup a cells environment add the following to your `localrc`:
244 244
     enable_service n-cell
245 245
 
246 246
 Be aware that there are some features currently missing in cells, one notable one being security groups.  The exercises have been patched to disable functionality not supported by cells.
247
+
248
+
249
+# Local Configuration
250
+
251
+Historically DevStack has used ``localrc`` to contain all local configuration and customizations. More and more of the configuration variables available for DevStack are passed-through to the individual project configuration files.  The old mechanism for this required specific code for each file and did not scale well.  This is handled now by a master local configuration file.
252
+
253
+# local.conf
254
+
255
+The new config file ``local.conf`` is an extended-INI format that introduces a new meta-section header that provides some additional information such as a phase name and destination config filename:
256
+
257
+  [[ <phase> | <filename> ]]
258
+
259
+where <phase> is one of a set of phase names defined by ``stack.sh`` and <filename> is the project config filename.  The filename is eval'ed in the stack.sh context so all environment variables are available and may be used.  Using the project config file variables in the header is strongly suggested (see example of NOVA_CONF below).  If the path of the config file does not exist it is skipped.
260
+
261
+The defined phases are:
262
+
263
+* local - extracts ``localrc`` from ``local.conf`` before ``stackrc`` is sourced
264
+* post-config - runs after the layer 2 services are configured and before they are started
265
+* extra - runs after services are started and before any files in ``extra.d`` are executes
266
+
267
+The file is processed strictly in sequence; meta-sections may be specified more than once but if any settings are duplicated the last to appear in the file will be used.
268
+
269
+  [[post-config|$NOVA_CONF]]
270
+  [DEFAULT]
271
+  use_syslog = True
272
+
273
+  [osapi_v3]
274
+  enabled = False
275
+
276
+A specific meta-section ``local:localrc`` is used to provide a default localrc file.  This allows all custom settings for DevStack to be contained in a single file.  ``localrc`` is not overwritten if it exists to preserve compatability.
277
+
278
+  [[local|localrc]]
279
+  FIXED_RANGE=10.254.1.0/24
280
+  ADMIN_PASSWORD=speciale
281
+  LOGFILE=$DEST/logs/stack.sh.log
282
+
283
+Note that ``Q_PLUGIN_CONF_FILE`` is unique in that it is assumed to _NOT_ start with a ``/`` (slash) character.  A slash will need to be added:
284
+
285
+  [[post-config|/$Q_PLUGIN_CONF_FILE]]
... ...
@@ -155,6 +155,22 @@ function err_if_not_set() {
155 155
 }
156 156
 
157 157
 
158
+# Prints line number and "message" in warning format
159
+# warn $LINENO "message"
160
+function warn() {
161
+    local exitcode=$?
162
+    errXTRACE=$(set +o | grep xtrace)
163
+    set +o xtrace
164
+    local msg="[WARNING] ${BASH_SOURCE[2]}:$1 $2"
165
+    echo $msg 1>&2;
166
+    if [[ -n ${SCREEN_LOGDIR} ]]; then
167
+        echo $msg >> "${SCREEN_LOGDIR}/error.log"
168
+    fi
169
+    $errXTRACE
170
+    return $exitcode
171
+}
172
+
173
+
158 174
 # HTTP and HTTPS proxy servers are supported via the usual environment variables [1]
159 175
 # ``http_proxy``, ``https_proxy`` and ``no_proxy``. They can be set in
160 176
 # ``localrc`` or on the command line if necessary::
161 177
new file mode 100644
... ...
@@ -0,0 +1,130 @@
0
+# lib/config - Configuration file manipulation functions
1
+
2
+# These functions have no external dependencies and the following side-effects:
3
+#
4
+# CONFIG_AWK_CMD is defined, default is ``awk``
5
+
6
+# Meta-config files contain multiple INI-style configuration files
7
+# using a specific new section header to delimit them:
8
+#
9
+#   [[group-name|file-name]]
10
+#
11
+# group-name refers to the group of configuration file changes to be processed
12
+# at a particular time.  These are called phases in ``stack.sh`` but 
13
+# group here as these functions are not DevStack-specific.
14
+#
15
+# file-name is the destination of the config file
16
+
17
+# Save trace setting
18
+C_XTRACE=$(set +o | grep xtrace)
19
+set +o xtrace
20
+
21
+
22
+# Allow the awk command to be overridden on legacy platforms
23
+CONFIG_AWK_CMD=${CONFIG_AWK_CMD:-awk}
24
+
25
+# Get the section for the specific group and config file
26
+# get_meta_section infile group configfile
27
+function get_meta_section() {
28
+    local file=$1
29
+    local matchgroup=$2
30
+    local configfile=$3
31
+
32
+    [[ -r $file ]] || return 0
33
+    [[ -z $configfile ]] && return 0
34
+
35
+    $CONFIG_AWK_CMD -v matchgroup=$matchgroup -v configfile=$configfile '
36
+        BEGIN { group = "" }
37
+        /^\[\[.+|.*\]\]/ {
38
+            if (group == "") {
39
+                gsub("[][]", "", $1);
40
+                split($1, a, "|");
41
+                if (a[1] == matchgroup && a[2] == configfile) {
42
+                    group=a[1]
43
+                }
44
+            } else {
45
+                group=""
46
+            }
47
+            next
48
+        }
49
+        {
50
+            if (group != "")
51
+                print $0
52
+        }
53
+    ' $file
54
+}
55
+
56
+
57
+# Get a list of config files for a specific group
58
+# get_meta_section_files infile group
59
+function get_meta_section_files() {
60
+    local file=$1
61
+    local matchgroup=$2
62
+
63
+    [[ -r $file ]] || return 0
64
+
65
+    $CONFIG_AWK_CMD -v matchgroup=$matchgroup '
66
+      /^\[\[.+\|.*\]\]/ {
67
+          gsub("[][]", "", $1);
68
+          split($1, a, "|");
69
+          if (a[1] == matchgroup)
70
+              print a[2]
71
+      }
72
+    ' $file
73
+}
74
+
75
+
76
+# Merge the contents of a meta-config file into its destination config file
77
+# If configfile does not exist it will be created.
78
+# merge_config_file infile group configfile
79
+function merge_config_file() {
80
+    local file=$1
81
+    local matchgroup=$2
82
+    local configfile=$3
83
+
84
+    [[ -r $configfile ]] || touch $configfile
85
+
86
+    get_meta_section $file $matchgroup $configfile | \
87
+    $CONFIG_AWK_CMD -v configfile=$configfile '
88
+        BEGIN { section = "" }
89
+        /^\[.+\]/ {
90
+            gsub("[][]", "", $1);
91
+            section=$1
92
+            next
93
+        }
94
+        /^ *\#/ {
95
+            next
96
+        }
97
+        /^.+/ {
98
+            split($0, d, " *= *")
99
+            print "iniset " configfile " " section " " d[1] " \"" d[2] "\""
100
+        }
101
+    ' | while read a; do eval "$a"; done
102
+
103
+}
104
+
105
+
106
+# Merge all of the files specified by group
107
+# merge_config_group infile group [group ...]
108
+function merge_config_group() {
109
+    local localfile=$1; shift
110
+    local matchgroups=$@
111
+
112
+    [[ -r $localfile ]] || return 0
113
+
114
+    for group in $matchgroups; do
115
+        for configfile in $(get_meta_section_files $localfile $group); do
116
+            if [[ -d $(dirname $configfile) ]]; then
117
+                merge_config_file $localfile $group $configfile
118
+            fi
119
+        done
120
+    done
121
+}
122
+
123
+
124
+# Restore xtrace
125
+$C_XTRACE
126
+
127
+# Local variables:
128
+# mode: shell-script
129
+# End:
... ...
@@ -29,6 +29,9 @@ TOP_DIR=$(cd $(dirname "$0") && pwd)
29 29
 # Import common functions
30 30
 source $TOP_DIR/functions
31 31
 
32
+# Import config functions
33
+source $TOP_DIR/lib/config
34
+
32 35
 # Determine what system we are running on.  This provides ``os_VENDOR``,
33 36
 # ``os_RELEASE``, ``os_UPDATE``, ``os_PACKAGE``, ``os_CODENAME``
34 37
 # and ``DISTRO``
... ...
@@ -38,6 +41,25 @@ GetDistro
38 38
 # Global Settings
39 39
 # ===============
40 40
 
41
+# Check for a ``localrc`` section embedded in ``local.conf`` and extract if
42
+# ``localrc`` does not already exist
43
+
44
+# Phase: local
45
+rm -f $TOP_DIR/.localrc.auto
46
+if [[ -r $TOP_DIR/local.conf ]]; then
47
+    LRC=$(get_meta_section_files $TOP_DIR/local.conf local)
48
+    for lfile in $LRC; do
49
+        if [[ "$lfile" == "localrc" ]]; then
50
+            if [[ -r $TOP_DIR/localrc ]]; then
51
+                warn $LINENO "localrc and local.conf:[[local]] both exist, using localrc"
52
+            else
53
+                echo "# Generated file, do not exit" >$TOP_DIR/.localrc.auto
54
+                get_meta_section $TOP_DIR/local.conf local $lfile >>$TOP_DIR/.localrc.auto
55
+            fi
56
+        fi
57
+    done
58
+fi
59
+
41 60
 # ``stack.sh`` is customizable by setting environment variables.  Override a
42 61
 # default setting via export::
43 62
 #
... ...
@@ -842,6 +864,9 @@ if is_service_enabled sysstat;then
842 842
 fi
843 843
 
844 844
 
845
+# Start Services
846
+# ==============
847
+
845 848
 # Keystone
846 849
 # --------
847 850
 
... ...
@@ -1153,6 +1178,14 @@ if is_service_enabled nova && is_baremetal; then
1153 1153
 fi
1154 1154
 
1155 1155
 
1156
+# Local Configuration
1157
+# ===================
1158
+
1159
+# Apply configuration from local.conf if it exists for layer 2 services
1160
+# Phase: post-config
1161
+merge_config_group $TOP_DIR/local.conf post-config
1162
+
1163
+
1156 1164
 # Launch Services
1157 1165
 # ===============
1158 1166
 
... ...
@@ -1348,6 +1381,14 @@ for i in BASE_SQL_CONN ENABLED_SERVICES HOST_IP LOGFILE \
1348 1348
 done
1349 1349
 
1350 1350
 
1351
+# Local Configuration
1352
+# ===================
1353
+
1354
+# Apply configuration from local.conf if it exists for layer 2 services
1355
+# Phase: extra
1356
+merge_config_group $TOP_DIR/local.conf extra
1357
+
1358
+
1351 1359
 # Run extras
1352 1360
 # ==========
1353 1361
 
... ...
@@ -1420,5 +1461,66 @@ if [[ -n "$DEPRECATED_TEXT" ]]; then
1420 1420
     echo_summary "WARNING: $DEPRECATED_TEXT"
1421 1421
 fi
1422 1422
 
1423
+# Specific warning for deprecated configs
1424
+if [[ -n "$EXTRA_OPTS" ]]; then
1425
+    echo ""
1426
+    echo_summary "WARNING: EXTRA_OPTS is used"
1427
+    echo "You are using EXTRA_OPTS to pass configuration into nova.conf."
1428
+    echo "Please convert that configuration in localrc to a nova.conf section in local.conf:"
1429
+    echo "
1430
+[[post-config|\$NOVA_CONF]]
1431
+[DEFAULT]
1432
+"
1433
+    for I in "${EXTRA_OPTS[@]}"; do
1434
+        # Replace the first '=' with ' ' for iniset syntax
1435
+        echo ${I}
1436
+    done
1437
+fi
1438
+
1439
+if [[ -n "$EXTRA_BAREMETAL_OPTS" ]]; then
1440
+    echo ""
1441
+    echo_summary "WARNING: EXTRA_OPTS is used"
1442
+    echo "You are using EXTRA_OPTS to pass configuration into nova.conf."
1443
+    echo "Please convert that configuration in localrc to a nova.conf section in local.conf:"
1444
+    echo "
1445
+[[post-config|\$NOVA_CONF]]
1446
+[baremetal]
1447
+"
1448
+    for I in "${EXTRA_BAREMETAL_OPTS[@]}"; do
1449
+        # Replace the first '=' with ' ' for iniset syntax
1450
+        echo ${I}
1451
+    done
1452
+fi
1453
+
1454
+if [[ -n "$Q_DHCP_EXTRA_DEFAULT_OPTS" ]]; then
1455
+    echo ""
1456
+    echo_summary "WARNING: Q_DHCP_EXTRA_DEFAULT_OPTS is used"
1457
+    echo "You are using Q_DHCP_EXTRA_DEFAULT_OPTS to pass configuration into $Q_DHCP_CONF_FILE."
1458
+    echo "Please convert that configuration in localrc to a $Q_DHCP_CONF_FILE section in local.conf:"
1459
+    echo "
1460
+[[post-config|\$Q_DHCP_CONF_FILE]]
1461
+[DEFAULT]
1462
+"
1463
+    for I in "${Q_DHCP_EXTRA_DEFAULT_OPTS[@]}"; do
1464
+        # Replace the first '=' with ' ' for iniset syntax
1465
+        echo ${I}
1466
+    done
1467
+fi
1468
+
1469
+if [[ -n "$Q_SRV_EXTRA_DEFAULT_OPTS" ]]; then
1470
+    echo ""
1471
+    echo_summary "WARNING: Q_SRV_EXTRA_DEFAULT_OPTS is used"
1472
+    echo "You are using Q_SRV_EXTRA_DEFAULT_OPTS to pass configuration into $NEUTRON_CONF."
1473
+    echo "Please convert that configuration in localrc to a $NEUTRON_CONF section in local.conf:"
1474
+    echo "
1475
+[[post-config|\$NEUTRON_CONF]]
1476
+[DEFAULT]
1477
+"
1478
+    for I in "${Q_SRV_EXTRA_DEFAULT_OPTS[@]}"; do
1479
+        # Replace the first '=' with ' ' for iniset syntax
1480
+        echo ${I}
1481
+    done
1482
+fi
1483
+
1423 1484
 # Indicate how long this took to run (bash maintained variable ``SECONDS``)
1424 1485
 echo_summary "stack.sh completed in $SECONDS seconds."
... ...
@@ -48,8 +48,12 @@ IDENTITY_API_VERSION=2.0
48 48
 USE_SCREEN=True
49 49
 
50 50
 # allow local overrides of env variables, including repo config
51
-if [ -f $RC_DIR/localrc ]; then
51
+if [[ -f $RC_DIR/localrc ]]; then
52
+    # Old-style user-supplied config
52 53
     source $RC_DIR/localrc
54
+elif [[ -f $RC_DIR/.localrc.auto ]]; then
55
+    # New-style user-supplied config extracted from local.conf
56
+    source $RC_DIR/.localrc.auto
53 57
 fi
54 58
 
55 59
 
56 60
new file mode 100755
... ...
@@ -0,0 +1,179 @@
0
+#!/usr/bin/env bash
1
+
2
+# Tests for DevStack meta-config functions
3
+
4
+TOP=$(cd $(dirname "$0")/.. && pwd)
5
+
6
+# Import common functions
7
+source $TOP/functions
8
+
9
+# Import config functions
10
+source $TOP/lib/config
11
+
12
+# check_result() tests and reports the result values
13
+# check_result "actual" "expected"
14
+function check_result() {
15
+    local actual=$1
16
+    local expected=$2
17
+    if [[ "$actual" == "$expected" ]]; then
18
+        echo "OK"
19
+    else
20
+        echo -e "failed: $actual != $expected\n"
21
+    fi
22
+}
23
+
24
+TEST_1C_ADD="[eee]
25
+type=new
26
+multi = foo2"
27
+
28
+function create_test1c() {
29
+    cat >test1c.conf <<EOF
30
+[eee]
31
+# original comment
32
+type=original
33
+EOF
34
+}
35
+
36
+function create_test2a() {
37
+    cat >test2a.conf <<EOF
38
+[ddd]
39
+# original comment
40
+type=original
41
+EOF
42
+}
43
+
44
+cat >test.conf <<EOF
45
+[[test1|test1a.conf]]
46
+[default]
47
+# comment an option
48
+#log_file=./log.conf
49
+log_file=/etc/log.conf
50
+handlers=do not disturb
51
+
52
+[aaa]
53
+# the commented option should not change
54
+#handlers=cc,dd
55
+handlers = aa, bb
56
+
57
+[[test1|test1b.conf]]
58
+[bbb]
59
+handlers=ee,ff
60
+
61
+[ ccc ]
62
+spaces  =  yes
63
+
64
+[[test2|test2a.conf]]
65
+[ddd]
66
+# new comment
67
+type=new
68
+additional=true
69
+
70
+[[test1|test1c.conf]]
71
+$TEST_1C_ADD
72
+EOF
73
+
74
+
75
+echo -n "get_meta_section_files: test0 doesn't exist: "
76
+VAL=$(get_meta_section_files test.conf test0)
77
+check_result "$VAL" ""
78
+
79
+echo -n "get_meta_section_files: test1 3 files: "
80
+VAL=$(get_meta_section_files test.conf test1)
81
+EXPECT_VAL="test1a.conf
82
+test1b.conf
83
+test1c.conf"
84
+check_result "$VAL" "$EXPECT_VAL"
85
+
86
+echo -n "get_meta_section_files: test2 1 file: "
87
+VAL=$(get_meta_section_files test.conf test2)
88
+EXPECT_VAL="test2a.conf"
89
+check_result "$VAL" "$EXPECT_VAL"
90
+
91
+
92
+# Get a section from a group that doesn't exist
93
+echo -n "get_meta_section: test0 doesn't exist: "
94
+VAL=$(get_meta_section test.conf test0 test0.conf)
95
+check_result "$VAL" ""
96
+
97
+# Get a single section from a group with multiple files
98
+echo -n "get_meta_section: test1c single section: "
99
+VAL=$(get_meta_section test.conf test1 test1c.conf)
100
+check_result "$VAL" "$TEST_1C_ADD"
101
+
102
+# Get a single section from a group with a single file
103
+echo -n "get_meta_section: test2a single section: "
104
+VAL=$(get_meta_section test.conf test2 test2a.conf)
105
+EXPECT_VAL="[ddd]
106
+# new comment
107
+type=new
108
+additional=true"
109
+check_result "$VAL" "$EXPECT_VAL"
110
+
111
+# Get a single section that doesn't exist from a group
112
+echo -n "get_meta_section: test2z.conf not in test2: "
113
+VAL=$(get_meta_section test.conf test2 test2z.conf)
114
+check_result "$VAL" ""
115
+
116
+# Get a section from a conf file that doesn't exist
117
+echo -n "get_meta_section: nofile doesn't exist: "
118
+VAL=$(get_meta_section nofile.ini test1)
119
+check_result "$VAL" ""
120
+
121
+echo -n "get_meta_section: nofile doesn't exist: "
122
+VAL=$(get_meta_section nofile.ini test0 test0.conf)
123
+check_result "$VAL" ""
124
+
125
+echo -n "merge_config_file test1c exists: "
126
+create_test1c
127
+merge_config_file test.conf test1 test1c.conf
128
+VAL=$(cat test1c.conf)
129
+# iniset adds values immediately under the section header
130
+EXPECT_VAL="[eee]
131
+multi = foo2
132
+# original comment
133
+type=new"
134
+check_result "$VAL" "$EXPECT_VAL"
135
+
136
+echo -n "merge_config_file test2a exists: "
137
+create_test2a
138
+merge_config_file test.conf test2 test2a.conf
139
+VAL=$(cat test2a.conf)
140
+# iniset adds values immediately under the section header
141
+EXPECT_VAL="[ddd]
142
+additional = true
143
+# original comment
144
+type=new"
145
+check_result "$VAL" "$EXPECT_VAL"
146
+
147
+echo -n "merge_config_file test2a not exist: "
148
+rm test2a.conf
149
+merge_config_file test.conf test2 test2a.conf
150
+VAL=$(cat test2a.conf)
151
+# iniset adds a blank line if it creates the file...
152
+EXPECT_VAL="
153
+[ddd]
154
+additional = true
155
+type = new"
156
+check_result "$VAL" "$EXPECT_VAL"
157
+
158
+echo -n "merge_config_group test2: "
159
+rm test2a.conf
160
+merge_config_group test.conf test2
161
+VAL=$(cat test2a.conf)
162
+# iniset adds a blank line if it creates the file...
163
+EXPECT_VAL="
164
+[ddd]
165
+additional = true
166
+type = new"
167
+check_result "$VAL" "$EXPECT_VAL"
168
+
169
+echo -n "merge_config_group test2 no conf file: "
170
+rm test2a.conf
171
+merge_config_group x-test.conf test2
172
+if [[ ! -r test2a.conf ]]; then
173
+    echo "OK"
174
+else
175
+    echo "failed: $VAL != $EXPECT_VAL"
176
+fi
177
+
178
+rm -f test.conf test1c.conf test2a.conf