From ded3c040fa97ad77cd74ccb92f9ddcf2b3c692db Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Wed, 20 Sep 2023 09:14:17 +0100 Subject: [PATCH 001/128] kayobe-env: Unstick KOLLA_SOURCE_PATH and KOLLA_VENV_PATH The kayobe-env script does not update the KOLLA_SOURCE_PATH and KOLLA_VENV_PATH variables if they are already set. This can lead to dangerous and difficult to diagnose issues where Kayobe uses a different version of Kolla Ansible than expected. This change updates these variables each time the kayobe-env script is sourced. Change-Id: I3b4b0b611750b9c7846ff5f74554aee2f14939e4 Closes-Bug: #2036711 (cherry picked from commit 651b8be1a0a2dd38ab46253a0c4a9c7d617cf7bc) --- kayobe-env | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kayobe-env b/kayobe-env index 5137927e5..28b1cccdb 100644 --- a/kayobe-env +++ b/kayobe-env @@ -30,8 +30,8 @@ export KOLLA_CONFIG_PATH=$KAYOBE_CONFIG_ROOT/etc/kolla # kayobe/ # kolla-ansible/ base_path=$(realpath $KAYOBE_CONFIG_ROOT/../../) -export KOLLA_SOURCE_PATH=${KOLLA_SOURCE_PATH:-${base_path}/src/kolla-ansible} -export KOLLA_VENV_PATH=${KOLLA_VENV_PATH:-${base_path}/venvs/kolla-ansible} +export KOLLA_SOURCE_PATH=${base_path}/src/kolla-ansible +export KOLLA_VENV_PATH=${base_path}/venvs/kolla-ansible function check_and_export_env { # Look for existing Kayobe environments From d1a47be025af96a9457d72be602dc5d0f1f3e4be Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Tue, 28 Nov 2023 16:54:37 +0000 Subject: [PATCH 002/128] Fix OSD summary pie chart --- .../grafana/dashboards/ceph/ceph_osds.json | 381 ++++++++++++------ ...xes-osd-size-summary-9924ef4aac61d2b6.yaml | 4 + 2 files changed, 256 insertions(+), 129 deletions(-) create mode 100644 etc/kayobe/kolla/config/grafana/dashboards/ceph/releasenotes/notes/fixes-osd-size-summary-9924ef4aac61d2b6.yaml diff --git a/etc/kayobe/kolla/config/grafana/dashboards/ceph/ceph_osds.json b/etc/kayobe/kolla/config/grafana/dashboards/ceph/ceph_osds.json index 6c6f525a7..826701984 100644 --- a/etc/kayobe/kolla/config/grafana/dashboards/ceph/ceph_osds.json +++ b/etc/kayobe/kolla/config/grafana/dashboards/ceph/ceph_osds.json @@ -1,10 +1,47 @@ {% raw %} { + "__inputs": [], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "10.1.4" + }, + { + "type": "panel", + "id": "graph", + "name": "Graph (old)", + "version": "" + }, + { + "type": "panel", + "id": "piechart", + "name": "Pie chart", + "version": "" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "table-old", + "name": "Table (old)", + "version": "" + } + ], "annotations": { "list": [ { "builtIn": 1, - "datasource": "-- Grafana --", + "datasource": { + "type": "datasource", + "uid": "grafana" + }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", @@ -14,11 +51,11 @@ ] }, "editable": true, - "gnetId": null, + "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": 191, - "iteration": 1616693984817, + "id": null, "links": [], + "liveNow": false, "panels": [ { "aliasColors": { @@ -27,7 +64,15 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, "fill": 1, "fillGradient": 0, "gridPos": { @@ -52,9 +97,10 @@ "links": [], "nullPointMode": "null", "options": { - "dataLinks": [] + "alertThreshold": true }, "percentage": false, + "pluginVersion": "10.1.4", "pointradius": 5, "points": false, "renderer": "flot", @@ -64,6 +110,9 @@ "steppedLine": false, "targets": [ { + "datasource": { + "uid": "$datasource" + }, "expr": "avg (irate(ceph_osd_op_r_latency_sum[5m]) / on (ceph_daemon) irate(ceph_osd_op_r_latency_count[5m]) * 1000)", "format": "time_series", "intervalFactor": 1, @@ -71,6 +120,9 @@ "refId": "A" }, { + "datasource": { + "uid": "$datasource" + }, "expr": "max (irate(ceph_osd_op_r_latency_sum[5m]) / on (ceph_daemon) irate(ceph_osd_op_r_latency_count[5m]) * 1000)", "format": "time_series", "intervalFactor": 1, @@ -78,6 +130,9 @@ "refId": "B" }, { + "datasource": { + "uid": "$datasource" + }, "expr": "quantile(0.95,\n (irate(ceph_osd_op_r_latency_sum[5m]) / on (ceph_daemon) irate(ceph_osd_op_r_latency_count[5m]) * 1000)\n)", "format": "time_series", "intervalFactor": 1, @@ -86,9 +141,7 @@ } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "OSD Read Latencies", "tooltip": { "shared": true, @@ -97,38 +150,32 @@ }, "type": "graph", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ms", - "label": null, "logBase": 1, - "max": null, "min": "0", "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": false } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { "columns": [], - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "description": "This table shows the osd's that are delivering the 10 highest read latencies within the cluster", "fontSize": "100%", "gridPos": { @@ -139,19 +186,15 @@ }, "id": 15, "links": [], - "options": {}, - "pageSize": null, "scroll": true, "showHeader": true, "sort": { - "col": null, "desc": false }, "styles": [ { "alias": "OSD ID", "align": "auto", - "colorMode": null, "colors": [ "rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", @@ -167,7 +210,6 @@ { "alias": "Latency (ms)", "align": "auto", - "colorMode": null, "colors": [ "rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", @@ -183,7 +225,6 @@ { "alias": "", "align": "auto", - "colorMode": null, "colors": [ "rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", @@ -199,6 +240,9 @@ ], "targets": [ { + "datasource": { + "uid": "$datasource" + }, "expr": "topk(10,\n (sort(\n (irate(ceph_osd_op_r_latency_sum[5m]) / on (ceph_daemon) irate(ceph_osd_op_r_latency_count[5m]) * 1000)\n ))\n)\n\n", "format": "table", "instant": true, @@ -209,7 +253,7 @@ ], "title": "Highest READ Latencies", "transform": "table", - "type": "table" + "type": "table-old" }, { "aliasColors": { @@ -218,7 +262,15 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, "fill": 1, "fillGradient": 0, "gridPos": { @@ -243,9 +295,10 @@ "links": [], "nullPointMode": "null", "options": { - "dataLinks": [] + "alertThreshold": true }, "percentage": false, + "pluginVersion": "10.1.4", "pointradius": 5, "points": false, "renderer": "flot", @@ -255,6 +308,9 @@ "steppedLine": false, "targets": [ { + "datasource": { + "uid": "$datasource" + }, "expr": "avg (rate(ceph_osd_op_w_latency_sum[10m]) / on (ceph_daemon) rate(ceph_osd_op_w_latency_count[10m]) * 1000)", "format": "time_series", "intervalFactor": 1, @@ -262,6 +318,9 @@ "refId": "A" }, { + "datasource": { + "uid": "$datasource" + }, "expr": "max (irate(ceph_osd_op_w_latency_sum[5m]) / on (ceph_daemon) irate(ceph_osd_op_w_latency_count[5m]) * 1000)", "format": "time_series", "hide": false, @@ -270,6 +329,9 @@ "refId": "B" }, { + "datasource": { + "uid": "$datasource" + }, "expr": "quantile(0.95,\n (irate(ceph_osd_op_w_latency_sum[5m]) / on (ceph_daemon) irate(ceph_osd_op_w_latency_count[5m]) * 1000)\n)", "format": "time_series", "hide": false, @@ -279,9 +341,7 @@ } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "OSD Write Latencies", "tooltip": { "shared": true, @@ -290,38 +350,32 @@ }, "type": "graph", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ms", - "label": null, "logBase": 1, - "max": null, "min": "0", "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": false } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { "columns": [], - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "description": "This table shows the osd's that are delivering the 10 highest write latencies within the cluster", "fontSize": "100%", "gridPos": { @@ -332,19 +386,15 @@ }, "id": 16, "links": [], - "options": {}, - "pageSize": null, "scroll": true, "showHeader": true, "sort": { - "col": null, "desc": false }, "styles": [ { "alias": "OSD ID", "align": "auto", - "colorMode": null, "colors": [ "rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", @@ -360,7 +410,6 @@ { "alias": "Latency (ms)", "align": "auto", - "colorMode": null, "colors": [ "rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", @@ -376,7 +425,6 @@ { "alias": "", "align": "auto", - "colorMode": null, "colors": [ "rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", @@ -392,6 +440,9 @@ ], "targets": [ { + "datasource": { + "uid": "$datasource" + }, "expr": "topk(10,\n (sort(\n (irate(ceph_osd_op_w_latency_sum[5m]) / on (ceph_daemon) irate(ceph_osd_op_w_latency_count[5m]) * 1000)\n ))\n)\n\n", "format": "table", "instant": true, @@ -402,12 +453,13 @@ ], "title": "Highest WRITE Latencies", "transform": "table", - "type": "table" + "type": "table-old" }, { - "cacheTimeout": null, "columns": [], - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "fontSize": "100%", "gridPos": { "h": 8, @@ -417,8 +469,6 @@ }, "id": 2, "links": [], - "options": {}, - "pageSize": null, "pluginVersion": "6.6.1", "showHeader": true, "sort": { @@ -436,7 +486,6 @@ { "alias": "", "align": "right", - "colorMode": null, "colors": [ "rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", @@ -451,6 +500,9 @@ ], "targets": [ { + "datasource": { + "uid": "$datasource" + }, "expr": "count by (device_class) (ceph_osd_metadata)", "format": "table", "instant": true, @@ -459,16 +511,15 @@ "refId": "A" } ], - "timeFrom": null, - "timeShift": null, "title": "OSD Types Summary", "transform": "table", - "type": "table" + "type": "table-old" }, { - "cacheTimeout": null, "columns": [], - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "fontSize": "100%", "gridPos": { "h": 8, @@ -479,8 +530,6 @@ "hideTimeOverride": true, "id": 4, "links": [], - "options": {}, - "pageSize": null, "showHeader": true, "sort": { "col": 0, @@ -497,7 +546,6 @@ { "alias": "", "align": "right", - "colorMode": null, "colors": [ "rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", @@ -512,6 +560,9 @@ ], "targets": [ { + "datasource": { + "uid": "$datasource" + }, "expr": "count(ceph_bluefs_wal_total_bytes)", "format": "time_series", "instant": true, @@ -521,6 +572,9 @@ "step": 240 }, { + "datasource": { + "uid": "$datasource" + }, "expr": "count(ceph_osd_metadata) - count(ceph_bluefs_wal_total_bytes)", "format": "time_series", "instant": true, @@ -530,6 +584,9 @@ "step": 240 }, { + "datasource": { + "uid": "$datasource" + }, "expr": "absent(ceph_bluefs_wal_total_bytes)*count(ceph_osd_metadata)", "format": "time_series", "instant": true, @@ -539,18 +596,36 @@ "step": 240 } ], - "timeFrom": null, - "timeShift": null, "title": "OSD Objectstore Types", "transform": "timeseries_to_columns", - "type": "table" + "type": "table-old" }, { - "cacheTimeout": null, - "columns": [], - "datasource": "$datasource", + "datasource": { + "type": "prometheus", + "uid": "$datasource" + }, "description": "The pie chart shows the various OSD sizes used within the cluster", - "fontSize": "100%", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "decimals": 2, + "displayName": "", + "mappings": [], + "noValue": "0", + "unit": "short" + }, + "overrides": [] + }, "gridPos": { "h": 8, "w": 4, @@ -560,39 +635,35 @@ "hideTimeOverride": true, "id": 8, "links": [], - "options": {}, - "pageSize": null, - "showHeader": true, - "sort": { - "col": 0, - "desc": true - }, - "styles": [ - { - "alias": "Time", - "align": "auto", - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "pattern": "Time", - "type": "date" + "options": { + "displayLabels": [ + "name" + ], + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true }, - { - "alias": "", - "align": "right", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" ], - "decimals": 2, - "pattern": "/.*/", - "thresholds": [], - "type": "number", - "unit": "short" + "fields": "", + "values": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" } - ], + }, + "pluginVersion": "10.1.4", "targets": [ { + "datasource": { + "uid": "$datasource" + }, + "editorMode": "code", "expr": "count(ceph_osd_stat_bytes < 1099511627776)", "format": "time_series", "instant": true, @@ -602,6 +673,10 @@ "step": 2 }, { + "datasource": { + "uid": "$datasource" + }, + "editorMode": "code", "expr": "count(ceph_osd_stat_bytes >= 1099511627776 < 2199023255552)", "format": "time_series", "instant": true, @@ -611,6 +686,10 @@ "step": 2 }, { + "datasource": { + "uid": "$datasource" + }, + "editorMode": "code", "expr": "count(ceph_osd_stat_bytes >= 2199023255552 < 3298534883328)", "format": "time_series", "instant": true, @@ -620,6 +699,10 @@ "step": 2 }, { + "datasource": { + "uid": "$datasource" + }, + "editorMode": "code", "expr": "count(ceph_osd_stat_bytes >= 3298534883328 < 4398046511104)", "format": "time_series", "instant": true, @@ -629,6 +712,10 @@ "step": 2 }, { + "datasource": { + "uid": "$datasource" + }, + "editorMode": "code", "expr": "count(ceph_osd_stat_bytes >= 4398046511104 < 6597069766656)", "format": "time_series", "instant": true, @@ -638,7 +725,11 @@ "step": 2 }, { - "expr": "count(ceph_osd_stat_bytes >= 6597069766656 < 8796093022208)", + "datasource": { + "uid": "$datasource" + }, + "editorMode": "code", + "expr": "count(ceph_osd_stat_bytes >= 6597069766656 < 8796093022208) ", "format": "time_series", "instant": true, "intervalFactor": 2, @@ -647,6 +738,10 @@ "step": 2 }, { + "datasource": { + "uid": "$datasource" + }, + "editorMode": "code", "expr": "count(ceph_osd_stat_bytes >= 8796093022208 < 10995116277760)", "format": "time_series", "instant": true, @@ -656,6 +751,10 @@ "step": 2 }, { + "datasource": { + "uid": "$datasource" + }, + "editorMode": "code", "expr": "count(ceph_osd_stat_bytes >= 10995116277760 < 13194139533312)", "format": "time_series", "instant": true, @@ -665,6 +764,10 @@ "step": 2 }, { + "datasource": { + "uid": "$datasource" + }, + "editorMode": "code", "expr": "count(ceph_osd_stat_bytes >= 13194139533312)", "format": "time_series", "instant": true, @@ -674,19 +777,25 @@ "step": 2 } ], - "timeFrom": null, - "timeShift": null, "title": "OSD Size Summary", - "transform": "timeseries_to_columns", - "type": "table" + "transformations": [], + "type": "piechart" }, { "aliasColors": {}, "bars": true, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "description": "Each bar indicates the number of OSD's that have a PG count in a specific range as shown on the x axis.", + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, "fill": 1, "fillGradient": 0, "gridPos": { @@ -715,9 +824,10 @@ "links": [], "nullPointMode": "null", "options": { - "dataLinks": [] + "alertThreshold": true }, "percentage": false, + "pluginVersion": "10.1.4", "pointradius": 5, "points": false, "renderer": "flot", @@ -727,6 +837,9 @@ "steppedLine": false, "targets": [ { + "datasource": { + "uid": "$datasource" + }, "expr": "ceph_osd_numpg\n", "format": "time_series", "instant": true, @@ -736,9 +849,7 @@ } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "Distribution of PGs per OSD", "tooltip": { "shared": false, @@ -749,7 +860,6 @@ "xaxis": { "buckets": 20, "mode": "histogram", - "name": null, "show": true, "values": [ "total" @@ -761,27 +871,25 @@ "format": "short", "label": "# of OSDs", "logBase": 1, - "max": null, "min": "0", "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": false } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { "collapsed": false, - "datasource": null, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, "gridPos": { "h": 1, "w": 24, @@ -790,6 +898,15 @@ }, "id": 20, "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], "title": "R/W Profile", "type": "row" }, @@ -798,8 +915,16 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "description": "Show the read/write workload profile overtime", + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, "fill": 1, "fillGradient": 0, "gridPos": { @@ -824,9 +949,10 @@ "links": [], "nullPointMode": "null", "options": { - "dataLinks": [] + "alertThreshold": true }, "percentage": false, + "pluginVersion": "10.1.4", "pointradius": 5, "points": false, "renderer": "flot", @@ -836,6 +962,9 @@ "steppedLine": false, "targets": [ { + "datasource": { + "uid": "$datasource" + }, "expr": "round(sum(irate(ceph_pool_rd[5m])))", "format": "time_series", "intervalFactor": 1, @@ -843,6 +972,9 @@ "refId": "A" }, { + "datasource": { + "uid": "$datasource" + }, "expr": "round(sum(irate(ceph_pool_wr[5m])))", "format": "time_series", "intervalFactor": 1, @@ -851,9 +983,7 @@ } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "Read/Write Profile", "tooltip": { "shared": true, @@ -862,46 +992,38 @@ }, "type": "graph", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } } ], - "refresh": false, - "schemaVersion": 22, + "refresh": "", + "schemaVersion": 38, "style": "dark", "tags": [], "templating": { "list": [ { "current": { + "selected": false, "text": "Prometheus", - "value": "Prometheus" + "value": "PBFA97CFB590B2093" }, "hide": 0, "includeAll": false, @@ -949,6 +1071,7 @@ "timezone": "", "title": "Ceph OSD Overview", "uid": "lo02I1Aiz", - "version": 9 + "version": 1, + "weekStart": "" } {% endraw %} diff --git a/etc/kayobe/kolla/config/grafana/dashboards/ceph/releasenotes/notes/fixes-osd-size-summary-9924ef4aac61d2b6.yaml b/etc/kayobe/kolla/config/grafana/dashboards/ceph/releasenotes/notes/fixes-osd-size-summary-9924ef4aac61d2b6.yaml new file mode 100644 index 000000000..d2c9fd1d3 --- /dev/null +++ b/etc/kayobe/kolla/config/grafana/dashboards/ceph/releasenotes/notes/fixes-osd-size-summary-9924ef4aac61d2b6.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + Fix an issue with the OSD summary pie chart not showing any data. From b81ee745b261fd7fd61144af54cbf988cff883d6 Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Tue, 5 Dec 2023 22:21:01 +0100 Subject: [PATCH 003/128] Add a note about network interfaces changes --- doc/source/operations/rocky-linux-9.rst | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/doc/source/operations/rocky-linux-9.rst b/doc/source/operations/rocky-linux-9.rst index 829d45e6f..d83497caf 100644 --- a/doc/source/operations/rocky-linux-9.rst +++ b/doc/source/operations/rocky-linux-9.rst @@ -242,6 +242,13 @@ Potential issues - If you are using hyper-converged Ceph, please also note the potential issues in the Storage section below. +- Network interface names may change between CentOS Stream 8 and Rocky Linux + 9, in which case you will need to update Kayobe configuration. Note that the + configuration should remain correct for hosts not yet migrated, otherwise + fact gathering may fail. For example, this can be done using ``host_vars``. + Once all hosts are migrated, the change can be moved to ``group_vars`` and + the temporary ``host_vars`` deleted. + Full procedure for one host --------------------------- @@ -367,7 +374,12 @@ The possible batches depend on a number of things: Potential issues ---------------- -Nothing yet! +- Network interface names may change between CentOS Stream 8 and Rocky Linux + 9, in which case you will need to update Kayobe configuration. Note that the + configuration should remain correct for hosts not yet migrated, otherwise + fact gathering may fail. For example, this can be done using ``host_vars``. + Once all hosts are migrated, the change can be moved to ``group_vars`` and + the temporary ``host_vars`` deleted. Full procedure for one batch of hosts ------------------------------------- @@ -518,6 +530,13 @@ Potential issues - Commands starting with ``ceph`` are all run on the cephadm bootstrap host in a cephadm shell unless stated otherwise. +- Network interface names may change between CentOS Stream 8 and Rocky Linux + 9, in which case you will need to update Kayobe configuration. Note that the + configuration should remain correct for hosts not yet migrated, otherwise + fact gathering may fail. For example, this can be done using ``host_vars``. + Once all hosts are migrated, the change can be moved to ``group_vars`` and + the temporary ``host_vars`` deleted. + Full procedure for any storage host ----------------------------------- From d422259ce1ff8fbc800379fe3be5f5d83052a431 Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Fri, 8 Dec 2023 12:10:48 +0100 Subject: [PATCH 004/128] Synchronise with kayobe stable/yoga Synchronise with kayobe @ 32b12be953d0c2f60970a95b82d0ebf846d0f86f. Change-Id: Ib4b75c084820f16ce245c1e1c50f10ccb2083537 --- etc/kayobe/bifrost.yml | 3 +++ etc/kayobe/compute.yml | 8 ++++---- etc/kayobe/controllers.yml | 8 ++++---- etc/kayobe/globals.yml | 5 ++--- etc/kayobe/infra-vms.yml | 16 +++++++++------- etc/kayobe/kolla.yml | 22 ++++++++++++++++++---- etc/kayobe/monitoring.yml | 6 +++--- etc/kayobe/seed-hypervisor.yml | 4 ++-- etc/kayobe/seed-vm.yml | 10 ++++++---- etc/kayobe/seed.yml | 12 ++++++++---- etc/kayobe/storage.yml | 8 ++++---- 11 files changed, 63 insertions(+), 39 deletions(-) diff --git a/etc/kayobe/bifrost.yml b/etc/kayobe/bifrost.yml index a9eba19dd..d15d18613 100644 --- a/etc/kayobe/bifrost.yml +++ b/etc/kayobe/bifrost.yml @@ -116,6 +116,9 @@ # Ironic inspector deployment ramdisk location. #kolla_bifrost_inspector_deploy_ramdisk: +# Ironic inspector legacy deployment kernel location. +#kolla_bifrost_inspector_legacy_deploy_kernel: + # Timeout of hardware inspection on overcloud nodes, in seconds. Default is # {{ inspector_inspection_timeout }}. #kolla_bifrost_inspection_timeout: diff --git a/etc/kayobe/compute.yml b/etc/kayobe/compute.yml index b1d8d6562..57286b40c 100644 --- a/etc/kayobe/compute.yml +++ b/etc/kayobe/compute.yml @@ -63,15 +63,15 @@ ############################################################################### # Compute node LVM configuration. -# List of compute volume groups. See mrlesmithjr.manage-lvm role for +# List of compute volume groups. See mrlesmithjr.manage_lvm role for # format. #compute_lvm_groups: -# Default list of compute volume groups. See mrlesmithjr.manage-lvm role for +# Default list of compute volume groups. See mrlesmithjr.manage_lvm role for # format. #compute_lvm_groups_default: -# Additional list of compute volume groups. See mrlesmithjr.manage-lvm role +# Additional list of compute volume groups. See mrlesmithjr.manage_lvm role # for format. #compute_lvm_groups_extra: @@ -82,7 +82,7 @@ # 'docker_storage_driver' is set to 'devicemapper', or false otherwise. #compute_lvm_group_data_enabled: -# Compute LVM volume group for data. See mrlesmithjr.manage-lvm role for +# Compute LVM volume group for data. See mrlesmithjr.manage_lvm role for # format. #compute_lvm_group_data: diff --git a/etc/kayobe/controllers.yml b/etc/kayobe/controllers.yml index 983251c6c..4780ec444 100644 --- a/etc/kayobe/controllers.yml +++ b/etc/kayobe/controllers.yml @@ -72,15 +72,15 @@ ############################################################################### # Controller node LVM configuration. -# List of controller volume groups. See mrlesmithjr.manage-lvm role for +# List of controller volume groups. See mrlesmithjr.manage_lvm role for # format. #controller_lvm_groups: -# Default list of controller volume groups. See mrlesmithjr.manage-lvm role for +# Default list of controller volume groups. See mrlesmithjr.manage_lvm role for # format. #controller_lvm_groups_default: -# Additional list of controller volume groups. See mrlesmithjr.manage-lvm role +# Additional list of controller volume groups. See mrlesmithjr.manage_lvm role # for format. #controller_lvm_groups_extra: @@ -91,7 +91,7 @@ # 'docker_storage_driver' is set to 'devicemapper', or false otherwise. #controller_lvm_group_data_enabled: -# Controller LVM volume group for data. See mrlesmithjr.manage-lvm role for +# Controller LVM volume group for data. See mrlesmithjr.manage_lvm role for # format. #controller_lvm_group_data: diff --git a/etc/kayobe/globals.yml b/etc/kayobe/globals.yml index 16b791548..ecdc5970b 100644 --- a/etc/kayobe/globals.yml +++ b/etc/kayobe/globals.yml @@ -4,8 +4,7 @@ ############################################################################### # Local path configuration (Ansible control host). -# Path to Kayobe configuration directory on Ansible control host, with an -# environment path appended if kayobe_environment is set. +# Path to Kayobe configuration directory on Ansible control host. #kayobe_config_path: # Name of Kayobe environment to use. Default is $KAYOBE_ENVIRONMENT, or an @@ -50,7 +49,7 @@ #os_distribution: # OS release. Valid options are "8-stream" when os_distribution is "centos", or -# "8" when os_distribution is "rocky", or "focal" and "jammy" when +# "8" or "9" when os_distribution is "rocky", or "focal" and "jammy" when # os_distribution is "ubuntu". #os_release: diff --git a/etc/kayobe/infra-vms.yml b/etc/kayobe/infra-vms.yml index a802f5acc..069c0877c 100644 --- a/etc/kayobe/infra-vms.yml +++ b/etc/kayobe/infra-vms.yml @@ -32,10 +32,12 @@ # Base image for the infra VM root volume. Default is # "https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img" # when os_distribution is "ubuntu", or -# https://dl.rockylinux.org/pub/rocky/8/images/Rocky-8-GenericCloud.latest.x86_64.qcow2 -# when os_distribution is "rocky", +# https://dl.rockylinux.org/pub/rocky/8/images/x86_64/Rocky-8-GenericCloud.latest.x86_64.qcow2 +# when os_distribution is "rocky" and os_release is "8" # or -# "https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-20220913.0.x86_64.qcow2" +# https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud.latest.x86_64.qcow2 +# when os_distribution is "rocky" and os_release is "9" +# "https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-latest.x86_64.qcow2" # otherwise. #infra_vm_root_image: @@ -92,15 +94,15 @@ ############################################################################### # Infrastructure VM node LVM configuration. -# List of infrastructure vm volume groups. See mrlesmithjr.manage-lvm role for +# List of infrastructure vm volume groups. See mrlesmithjr.manage_lvm role for # format. #infra_vm_lvm_groups: -# Default list of infrastructure vm volume groups. See mrlesmithjr.manage-lvm +# Default list of infrastructure vm volume groups. See mrlesmithjr.manage_lvm # role for format. #infra_vm_lvm_groups_default: -# Additional list of infrastructure vm volume groups. See mrlesmithjr.manage-lvm +# Additional list of infrastructure vm volume groups. See mrlesmithjr.manage_lvm # role for format. #infra_vm_lvm_groups_extra: @@ -111,7 +113,7 @@ # 'docker_storage_driver' is set to 'devicemapper', or false otherwise. #infra_vm_lvm_group_data_enabled: -# Infrastructure VM LVM volume group for data. See mrlesmithjr.manage-lvm role +# Infrastructure VM LVM volume group for data. See mrlesmithjr.manage_lvm role # for format. #infra_vm_lvm_group_data: diff --git a/etc/kayobe/kolla.yml b/etc/kayobe/kolla.yml index 16714ec39..1734696ea 100644 --- a/etc/kayobe/kolla.yml +++ b/etc/kayobe/kolla.yml @@ -67,8 +67,9 @@ # Kolla configuration. # Kolla base container image distribution. Options are "centos", "debian", -# "ubuntu". Default is -# {{ 'centos' if os_distribution == 'rocky' else os_distribution }}. +# "rocky", "ubuntu". Default is +# {{ 'centos' if (os_distribution == 'rocky' and os_release == '8') else +# os_distribution }}. #kolla_base_distro: # Kolla container image type: binary or source. Default is 'source'. @@ -298,6 +299,7 @@ #kolla_enable_gnocchi: #kolla_enable_gnocchi_statsd: #kolla_enable_grafana: +#kolla_enable_grafana_external: #kolla_enable_hacluster: #kolla_enable_haproxy: #kolla_enable_haproxy_memcached: @@ -337,6 +339,7 @@ #kolla_enable_keystone_federation: #kolla_enable_keystone_horizon_policy_file: #kolla_enable_kibana: +#kolla_enable_kibana_external: #kolla_enable_kuryr: #kolla_enable_loadbalancer: #kolla_enable_magnum: @@ -349,6 +352,8 @@ #kolla_enable_mariabackup: #kolla_enable_mariadb: #kolla_enable_masakari: +#kolla_enable_masakari_hostmonitor: +#kolla_enable_masakari_instancemonitor: #kolla_enable_memcached: #kolla_enable_mistral: #kolla_enable_monasca: @@ -379,6 +384,9 @@ #kolla_enable_nova_ssh: #kolla_enable_octavia: #kolla_enable_octavia_driver_agent: +#kolla_enable_opensearch: +#kolla_enable_opensearch_dashboards: +#kolla_enable_opensearch_dashboards_external: #kolla_enable_openstack_core: #kolla_enable_openvswitch: #kolla_enable_osprofiler: @@ -388,6 +396,7 @@ #kolla_enable_placement: #kolla_enable_prometheus: #kolla_enable_prometheus_alertmanager: +#kolla_enable_prometheus_alertmanager_external: #kolla_enable_prometheus_blackbox_exporter: #kolla_enable_prometheus_cadvisor: #kolla_enable_prometheus_ceph_mgr_exporter: @@ -397,6 +406,7 @@ #kolla_enable_prometheus_haproxy_exporter: #kolla_enable_prometheus_libvirt_exporter: #kolla_enable_prometheus_memcached_exporter: +#kolla_enable_prometheus_msteams: #kolla_enable_prometheus_mysqld_exporter: #kolla_enable_prometheus_node_exporter: #kolla_enable_prometheus_openstack_exporter: @@ -430,6 +440,10 @@ # Kolla passwords file. #kolla_ansible_default_custom_passwords: +# Dictionary containing extra custom passwords to add or override in the Kolla +# passwords file. +#kolla_ansible_extra_custom_passwords: + # Dictionary containing custom passwords to add or override in the Kolla # passwords file. #kolla_ansible_custom_passwords: @@ -469,7 +483,7 @@ # Path to a CA certificate file to use for the OS_CACERT environment variable # in public-openrc.sh file when TLS is enabled, instead of Kolla-Ansible's # default. -#kolla_external_fqdn_cacert: +#kolla_public_openrc_cacert: # Internal API certificate bundle. # @@ -482,7 +496,7 @@ # Path to a CA certificate file to use for the OS_CACERT environment variable # in admin-openrc.sh file when TLS is enabled, instead of Kolla-Ansible's # default. -#kolla_internal_fqdn_cacert: +#kolla_admin_openrc_cacert: ############################################################################### # Proxy configuration diff --git a/etc/kayobe/monitoring.yml b/etc/kayobe/monitoring.yml index f332ab938..5468936d3 100644 --- a/etc/kayobe/monitoring.yml +++ b/etc/kayobe/monitoring.yml @@ -63,15 +63,15 @@ ############################################################################### # Monitoring node LVM configuration. -# List of monitoring node volume groups. See mrlesmithjr.manage-lvm role for +# List of monitoring node volume groups. See mrlesmithjr.manage_lvm role for # format. #monitoring_lvm_groups: -# Default list of monitoring node volume groups. See mrlesmithjr.manage-lvm +# Default list of monitoring node volume groups. See mrlesmithjr.manage_lvm # role for format. #monitoring_lvm_groups_default: -# Additional list of monitoring node volume groups. See mrlesmithjr.manage-lvm +# Additional list of monitoring node volume groups. See mrlesmithjr.manage_lvm # role for format. #monitoring_lvm_groups_extra: diff --git a/etc/kayobe/seed-hypervisor.yml b/etc/kayobe/seed-hypervisor.yml index ac72fcd3d..dd8fbca23 100644 --- a/etc/kayobe/seed-hypervisor.yml +++ b/etc/kayobe/seed-hypervisor.yml @@ -36,7 +36,7 @@ ############################################################################### # Seed hypervisor node LVM configuration. -# List of seed hypervisor volume groups. See mrlesmithjr.manage-lvm role for +# List of seed hypervisor volume groups. See mrlesmithjr.manage_lvm role for # format. Set to "{{ seed_hypervisor_lvm_groups_with_data }}" to create a # volume group for libvirt storage. #seed_hypervisor_lvm_groups: @@ -45,7 +45,7 @@ # default. #seed_hypervisor_lvm_groups_with_data: -# Seed LVM volume group for data. See mrlesmithjr.manage-lvm role for format. +# Seed LVM volume group for data. See mrlesmithjr.manage_lvm role for format. #seed_hypervisor_lvm_group_data: # List of disks for use by seed hypervisor LVM data volume group. Default to an diff --git a/etc/kayobe/seed-vm.yml b/etc/kayobe/seed-vm.yml index 3856d51f2..24122b033 100644 --- a/etc/kayobe/seed-vm.yml +++ b/etc/kayobe/seed-vm.yml @@ -25,11 +25,13 @@ # Base image for the seed VM root volume. Default is # "https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img" -# when os_distribution is "ubuntu", -# https://dl.rockylinux.org/pub/rocky/8/images/Rocky-8-GenericCloud.latest.x86_64.qcow2 -# when os_distribution is "rocky", +# when os_distribution is "ubuntu", or +# https://dl.rockylinux.org/pub/rocky/8/images/x86_64/Rocky-8-GenericCloud.latest.x86_64.qcow2 +# when os_distribution is "rocky" and os_release is "8" # or -# "https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-20220913.0.x86_64.qcow2" +# https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud.latest.x86_64.qcow2 +# when os_distribution is "rocky" and os_release is "9" +# "https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-latest.x86_64.qcow2" # otherwise. #seed_vm_root_image: diff --git a/etc/kayobe/seed.yml b/etc/kayobe/seed.yml index ade99307d..bc86fa627 100644 --- a/etc/kayobe/seed.yml +++ b/etc/kayobe/seed.yml @@ -36,14 +36,14 @@ ############################################################################### # Seed node LVM configuration. -# List of seed volume groups. See mrlesmithjr.manage-lvm role for format. +# List of seed volume groups. See mrlesmithjr.manage_lvm role for format. #seed_lvm_groups: -# Default list of seed volume groups. See mrlesmithjr.manage-lvm role for +# Default list of seed volume groups. See mrlesmithjr.manage_lvm role for # format. #seed_lvm_groups_default: -# Additional list of seed volume groups. See mrlesmithjr.manage-lvm role for +# Additional list of seed volume groups. See mrlesmithjr.manage_lvm role for # format. #seed_lvm_groups_extra: @@ -54,7 +54,7 @@ # 'docker_storage_driver' is set to 'devicemapper', or false otherwise. #seed_lvm_group_data_enabled: -# Seed LVM volume group for data. See mrlesmithjr.manage-lvm role for format. +# Seed LVM volume group for data. See mrlesmithjr.manage_lvm role for format. #seed_lvm_group_data: # List of disks for use by seed LVM data volume group. Default to an invalid @@ -106,6 +106,10 @@ # #seed_containers: +# Whether to attempt a basic authentication login to a registry when +# deploying seed containers +#seed_deploy_containers_registry_attempt_login: + ############################################################################### # Seed node firewalld configuration. diff --git a/etc/kayobe/storage.yml b/etc/kayobe/storage.yml index 535666c95..e9e52dfe6 100644 --- a/etc/kayobe/storage.yml +++ b/etc/kayobe/storage.yml @@ -68,15 +68,15 @@ ############################################################################### # Storage node LVM configuration. -# List of storage volume groups. See mrlesmithjr.manage-lvm role for +# List of storage volume groups. See mrlesmithjr.manage_lvm role for # format. #storage_lvm_groups: -# Default list of storage volume groups. See mrlesmithjr.manage-lvm role for +# Default list of storage volume groups. See mrlesmithjr.manage_lvm role for # format. #storage_lvm_groups_default: -# Additional list of storage volume groups. See mrlesmithjr.manage-lvm role +# Additional list of storage volume groups. See mrlesmithjr.manage_lvm role # for format. #storage_lvm_groups_extra: @@ -87,7 +87,7 @@ # 'docker_storage_driver' is set to 'devicemapper', or false otherwise. #storage_lvm_group_data_enabled: -# Storage LVM volume group for data. See mrlesmithjr.manage-lvm role for +# Storage LVM volume group for data. See mrlesmithjr.manage_lvm role for # format. #storage_lvm_group_data: From 78c3257810533cd20a8e628aaebfa502d7618ac6 Mon Sep 17 00:00:00 2001 From: Matt Anson Date: Wed, 10 Jan 2024 16:11:50 +0000 Subject: [PATCH 005/128] Bump seed Pulp to 3.43.1 --- etc/kayobe/seed.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/kayobe/seed.yml b/etc/kayobe/seed.yml index 96fa86ac0..a63dad4a3 100644 --- a/etc/kayobe/seed.yml +++ b/etc/kayobe/seed.yml @@ -106,7 +106,7 @@ seed_pulp_container: image: pulp/pulp pre: "{{ kayobe_config_path }}/containers/pulp/pre.yml" post: "{{ kayobe_config_path }}/containers/pulp/post.yml" - tag: "3.24.0" + tag: "3.43.1" network_mode: host # Override deploy_containers_defaults.init == true to ensure # s6-overlay-suexec starts as pid 1 From ac809cbf077abaae8b5d544155adb03f1e60f3e0 Mon Sep 17 00:00:00 2001 From: Matt Crees Date: Fri, 19 Jan 2024 14:34:55 +0000 Subject: [PATCH 006/128] Skip docker registry login by default, login only when pulp is deployed --- etc/kayobe/containers/pulp/post.yml | 7 +++++++ .../environments/aufn-ceph/a-universe-from-nothing.sh | 2 +- etc/kayobe/seed.yml | 4 ++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/etc/kayobe/containers/pulp/post.yml b/etc/kayobe/containers/pulp/post.yml index fec1abb94..967c4e37d 100644 --- a/etc/kayobe/containers/pulp/post.yml +++ b/etc/kayobe/containers/pulp/post.yml @@ -27,3 +27,10 @@ when: - stackhpc_pulp_sync_for_local_container_build | bool - pulp_settings.changed + +- name: Login to docker registry + docker_login: + registry_url: "{{ kolla_docker_registry or omit }}" + username: "{{ kolla_docker_registry_username }}" + password: "{{ kolla_docker_registry_password }}" + reauthorize: yes diff --git a/etc/kayobe/environments/aufn-ceph/a-universe-from-nothing.sh b/etc/kayobe/environments/aufn-ceph/a-universe-from-nothing.sh index 1464e6127..03cd23439 100755 --- a/etc/kayobe/environments/aufn-ceph/a-universe-from-nothing.sh +++ b/etc/kayobe/environments/aufn-ceph/a-universe-from-nothing.sh @@ -87,7 +87,7 @@ kayobe seed vm provision kayobe seed host configure # Deploy local pulp server as a container on the seed VM -kayobe seed service deploy --tags seed-deploy-containers --kolla-tags none -e deploy_containers_registry_attempt_login=False +kayobe seed service deploy --tags seed-deploy-containers --kolla-tags # Deploying the seed restarts networking interface, run configure-local-networking.sh again to re-add routes. $KAYOBE_CONFIG_PATH/environments/$KAYOBE_ENVIRONMENT/configure-local-networking.sh diff --git a/etc/kayobe/seed.yml b/etc/kayobe/seed.yml index 96fa86ac0..524f6cff4 100644 --- a/etc/kayobe/seed.yml +++ b/etc/kayobe/seed.yml @@ -152,6 +152,10 @@ seed_containers: >- seed_extra_containers: {} +# Whether to attempt a basic authentication login to a registry when +# deploying seed containers +seed_deploy_containers_registry_attempt_login: "{{ not seed_pulp_container_enabled | bool }}" + ############################################################################### # Seed node firewalld configuration. From f70698b01429c7f13d1bca0b70f61bebad29f2c0 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Mon, 5 Feb 2024 16:06:31 +0000 Subject: [PATCH 007/128] Update .gitreview for unmaintained/yoga Change-Id: Ibd47c684580d35f58b0b715eab2e5110d17bb69a --- .gitreview | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitreview b/.gitreview index 9c0b64bdf..fa721c64d 100644 --- a/.gitreview +++ b/.gitreview @@ -2,4 +2,4 @@ host=review.opendev.org port=29418 project=openstack/kayobe-config.git -defaultbranch=stable/yoga +defaultbranch=unmaintained/yoga From 1b958baaad63fe8946b367e1054c4ab8b2516941 Mon Sep 17 00:00:00 2001 From: Seunghun Lee Date: Wed, 7 Feb 2024 11:46:21 +0000 Subject: [PATCH 008/128] Bump kolla image tags for ubuntu jammy 2023.1 except for neutron --- etc/kayobe/kolla-image-tags.yml | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/etc/kayobe/kolla-image-tags.yml b/etc/kayobe/kolla-image-tags.yml index b9fa290c1..24c19e32d 100644 --- a/etc/kayobe/kolla-image-tags.yml +++ b/etc/kayobe/kolla-image-tags.yml @@ -5,23 +5,11 @@ kolla_image_tags: openstack: rocky-9: 2023.1-rocky-9-20240202T105928 - ubuntu-jammy: 2023.1-ubuntu-jammy-20231011T200357 - bifrost: - ubuntu-jammy: 2023.1-ubuntu-jammy-20231228T140806 - cloudkitty: - ubuntu-jammy: 2023.1-ubuntu-jammy-20231115T110235 + ubuntu-jammy: 2023.1-ubuntu-jammy-20240129T151608 haproxy_ssh: - ubuntu-jammy: 2023.1-ubuntu-jammy-20240104T071640 rocky-9: 2023.1-rocky-9-20240205T162323 letsencrypt: - ubuntu-jammy: 2023.1-ubuntu-jammy-20240104T071640 rocky-9: 2023.1-rocky-9-20240205T162323 neutron: rocky-9: 2023.1-rocky-9-20240202T145927 ubuntu-jammy: 2023.1-ubuntu-jammy-20231220T222020 - nova: - ubuntu-jammy: 2023.1-ubuntu-jammy-20231220T222020 - octavia: - ubuntu-jammy: 2023.1-ubuntu-jammy-20231220T222020 - opensearch: - ubuntu-jammy: 2023.1-ubuntu-jammy-20231214T151917 From dd18412655368c5f6e0e575f54122599af7fa0dd Mon Sep 17 00:00:00 2001 From: Jake Hutchinson Date: Wed, 7 Feb 2024 17:11:14 +0000 Subject: [PATCH 009/128] Deprecate network configuration in environments --- etc/kayobe/environments/ci-aio/stackhpc-ci.yml | 7 ------- etc/kayobe/environments/ci-builder/stackhpc-ci.yml | 7 ------- etc/kayobe/environments/ci-multinode/stackhpc-ci.yml | 7 ------- 3 files changed, 21 deletions(-) diff --git a/etc/kayobe/environments/ci-aio/stackhpc-ci.yml b/etc/kayobe/environments/ci-aio/stackhpc-ci.yml index 9740da775..00ea5bc10 100644 --- a/etc/kayobe/environments/ci-aio/stackhpc-ci.yml +++ b/etc/kayobe/environments/ci-aio/stackhpc-ci.yml @@ -5,13 +5,6 @@ # Docker namespace to use for Kolla images. Default is 'kolla'. kolla_docker_namespace: stackhpc-dev -############################################################################### -# Network configuration. - -# Don't touch resolv.conf: use Neutron DNS for accessing Pulp server via -# hostname. -resolv_is_managed: false - ############################################################################### # StackHPC configuration. diff --git a/etc/kayobe/environments/ci-builder/stackhpc-ci.yml b/etc/kayobe/environments/ci-builder/stackhpc-ci.yml index efe4236e9..e0421f97d 100644 --- a/etc/kayobe/environments/ci-builder/stackhpc-ci.yml +++ b/etc/kayobe/environments/ci-builder/stackhpc-ci.yml @@ -30,13 +30,6 @@ kolla_enable_prometheus: true kolla_enable_redis: true kolla_enable_skydive: true -############################################################################### -# Network configuration. - -# Don't touch resolv.conf: use Neutron DNS for accessing Pulp server via -# hostname. -resolv_is_managed: false - ############################################################################### # StackHPC configuration. diff --git a/etc/kayobe/environments/ci-multinode/stackhpc-ci.yml b/etc/kayobe/environments/ci-multinode/stackhpc-ci.yml index b37db2ee7..cdb6eb810 100644 --- a/etc/kayobe/environments/ci-multinode/stackhpc-ci.yml +++ b/etc/kayobe/environments/ci-multinode/stackhpc-ci.yml @@ -5,13 +5,6 @@ # Docker namespace to use for Kolla images. Default is 'kolla'. kolla_docker_namespace: stackhpc-dev -############################################################################### -# Network configuration. - -# Don't touch resolv.conf: use Neutron DNS for accessing Pulp server via -# hostname. -resolv_is_managed: false - ############################################################################### # StackHPC configuration. From 2268afff477c1c675bcd4147ffd37690d7b89009 Mon Sep 17 00:00:00 2001 From: Seunghun Lee Date: Wed, 7 Feb 2024 11:47:46 +0000 Subject: [PATCH 010/128] Upgrade neutron kolla tag but ovs is not included --- etc/kayobe/kolla-image-tags.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/etc/kayobe/kolla-image-tags.yml b/etc/kayobe/kolla-image-tags.yml index 24c19e32d..3f095e7e1 100644 --- a/etc/kayobe/kolla-image-tags.yml +++ b/etc/kayobe/kolla-image-tags.yml @@ -12,4 +12,3 @@ kolla_image_tags: rocky-9: 2023.1-rocky-9-20240205T162323 neutron: rocky-9: 2023.1-rocky-9-20240202T145927 - ubuntu-jammy: 2023.1-ubuntu-jammy-20231220T222020 From 225710316547209979d9b5a56fd141676da03613 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Tue, 13 Feb 2024 16:25:35 +0000 Subject: [PATCH 011/128] CI: Fail aio job when Terraform reaches max attempts Previously the script exited 0 at the end of the loop, then failed in a later step. --- .github/workflows/stackhpc-all-in-one.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/stackhpc-all-in-one.yml b/.github/workflows/stackhpc-all-in-one.yml index 704009412..de3c25f50 100644 --- a/.github/workflows/stackhpc-all-in-one.yml +++ b/.github/workflows/stackhpc-all-in-one.yml @@ -158,13 +158,15 @@ jobs: for attempt in $(seq 5); do if terraform apply -auto-approve; then echo "Created infrastructure on attempt $attempt" - break + exit 0 fi echo "Failed to create infrastructure on attempt $attempt" sleep 10 terraform destroy -auto-approve sleep 60 done + echo "Failed to create infrastructure after $attempt attempts" + exit 1 working-directory: ${{ github.workspace }}/terraform/aio env: OS_CLOUD: ${{ inputs.OS_CLOUD }} From 56abaa7744f3278fd0a2ddb1763fbdd8852406a1 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Wed, 14 Feb 2024 14:46:36 +0000 Subject: [PATCH 012/128] Reduce Elasticsearch/OpenSearch heap size to 200m in ci-aio env This should free up some memory, allowing us to use a smaller flavor. --- etc/kayobe/environments/ci-aio/kolla/globals.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/etc/kayobe/environments/ci-aio/kolla/globals.yml b/etc/kayobe/environments/ci-aio/kolla/globals.yml index b717d6e54..6b42acdea 100644 --- a/etc/kayobe/environments/ci-aio/kolla/globals.yml +++ b/etc/kayobe/environments/ci-aio/kolla/globals.yml @@ -13,8 +13,8 @@ docker_yum_baseurl: "{{ stackhpc_repo_docker_url }}" docker_yum_gpgkey: "https://download.docker.com/linux/centos/gpg" # Elasticsearch / OpenSearch memory tuning -es_heap_size: 1g -opensearch_heap_size: 1g +es_heap_size: 200m +opensearch_heap_size: 200m # Increase Grafana timeout grafana_start_first_node_retries: 20 From b293231e3fd96f2b4d0c01c95fdea1cc3a8c8fd7 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Wed, 14 Feb 2024 14:46:53 +0000 Subject: [PATCH 013/128] Disable Heat in ci-aio env This should free up some memory, allowing us to use a smaller flavor. --- etc/kayobe/environments/ci-aio/stackhpc-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/etc/kayobe/environments/ci-aio/stackhpc-ci.yml b/etc/kayobe/environments/ci-aio/stackhpc-ci.yml index 6050bffd2..d8f4ef7de 100644 --- a/etc/kayobe/environments/ci-aio/stackhpc-ci.yml +++ b/etc/kayobe/environments/ci-aio/stackhpc-ci.yml @@ -5,6 +5,9 @@ # Docker namespace to use for Kolla images. Default is 'kolla'. kolla_docker_namespace: stackhpc-dev +# Disable some services to reduce memory footprint. +kolla_enable_heat: false + ############################################################################### # Network configuration. From 23247d1a2727a4121646036d8897a0c01d9ce2de Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Wed, 14 Feb 2024 14:47:30 +0000 Subject: [PATCH 014/128] Reduce aio flavor to en1.medium This flavor has 8G memory and is cheaper than en1.large. --- .github/workflows/stackhpc-all-in-one.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stackhpc-all-in-one.yml b/.github/workflows/stackhpc-all-in-one.yml index de3c25f50..1155af7a1 100644 --- a/.github/workflows/stackhpc-all-in-one.yml +++ b/.github/workflows/stackhpc-all-in-one.yml @@ -38,7 +38,7 @@ on: vm_flavor: description: Flavor for the all-in-one VM type: string - default: en1.large + default: en1.medium vm_network: description: Network for the all-in-one VM type: string From 1d75be487e29cf7981eff474b8c6295091b23477 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Wed, 14 Feb 2024 13:57:39 +0000 Subject: [PATCH 015/128] CI: Add tags to aio VMs This will make it easier to identify them. --- .github/workflows/stackhpc-all-in-one.yml | 2 ++ terraform/aio/vm.tf | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/.github/workflows/stackhpc-all-in-one.yml b/.github/workflows/stackhpc-all-in-one.yml index de3c25f50..211b781c0 100644 --- a/.github/workflows/stackhpc-all-in-one.yml +++ b/.github/workflows/stackhpc-all-in-one.yml @@ -134,6 +134,7 @@ jobs: aio_vm_flavor = "${{ env.VM_FLAVOR }}" aio_vm_network = "${{ env.VM_NETWORK }}" aio_vm_subnet = "${{ env.VM_SUBNET }}" + aio_vm_tags = ${{ env.VM_TAGS }} EOF working-directory: ${{ github.workspace }}/terraform/aio env: @@ -144,6 +145,7 @@ jobs: VM_NETWORK: ${{ inputs.vm_network }} VM_SUBNET: ${{ inputs.vm_subnet }} VM_INTERFACE: ${{ inputs.vm_interface }} + VM_TAGS: '["skc-ci-aio", "PR=${{ github.event.number }}"]' - name: Terraform Plan run: terraform plan diff --git a/terraform/aio/vm.tf b/terraform/aio/vm.tf index 676aff1c3..36dfa50a5 100644 --- a/terraform/aio/vm.tf +++ b/terraform/aio/vm.tf @@ -38,6 +38,11 @@ variable "aio_vm_volume_size" { default = 35 } +variable "aio_vm_tags" { + type = list(string) + default = [] +} + locals { image_is_uuid = length(regexall("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", var.aio_vm_image)) > 0 } @@ -69,6 +74,8 @@ resource "openstack_compute_instance_v2" "kayobe-aio" { destination_type = "volume" delete_on_termination = true } + + tags = var.aio_vm_tags } # Wait for the instance to be accessible via SSH before progressing. From bf7649ec12857b86a610e64a3c3994d70d5f2574 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Wed, 14 Feb 2024 14:33:27 +0000 Subject: [PATCH 016/128] CI: Add a workflow to clean up stale instances Sometimes we can end up with aio instances left running indefinitely. This can lead to unnecessary cloud costs. This change adds a periodic workflow that runs every 2 hours and deletes instances with the skc-ci-aio tag that are over 3 hours old. --- .github/workflows/stackhpc-ci-cleanup.yml | 57 +++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 .github/workflows/stackhpc-ci-cleanup.yml diff --git a/.github/workflows/stackhpc-ci-cleanup.yml b/.github/workflows/stackhpc-ci-cleanup.yml new file mode 100644 index 000000000..672500ab1 --- /dev/null +++ b/.github/workflows/stackhpc-ci-cleanup.yml @@ -0,0 +1,57 @@ +--- +name: Clean up stale CI resources +on: + schedule: + # Every 2 hours at quarter past + - cron: '15 0/2 * * *' + +jobs: + ci-cleanup: + name: Clean up stale CI resources + if: github.repository == 'stackhpc/stackhpc-kayobe-config' + runs-on: ubuntu-latest + permissions: {} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + path: src/kayobe-config + + - name: Setup Python + uses: actions/setup-python@v5 + + - name: Generate clouds.yaml + run: | + cat << EOF > clouds.yaml + ${{ secrets.CLOUDS_YAML }} + EOF + + - name: Determine OpenStack release + id: openstack_release + run: | + BRANCH=$(awk -F'=' '/defaultbranch/ {print $2}' src/kayobe-config/.gitreview) + echo "openstack_release=${BRANCH}" | sed "s|stable/||" >> $GITHUB_OUTPUT + + - name: Install OpenStack client + run: | + pip install python-openstackclient -c https://opendev.org/openstack/requirements/raw/branch/stable/${{ steps.openstack_release.outputs.openstack_release }}/upper-constraints.txt + + - name: Clean up aio instances over 3 hours old + run: | + result=0 + changes_before=$(date -Imin -d -3hours) + for status in ACTIVE ERROR SHUTOFF; do + for instance in $(openstack server list --tags skc-ci-aio --os-compute-api-version 2.66 --format value --column Name --changes-before $changes_before --status $status); do + echo "Cleaning up $status instance $instance" + openstack server show $instance + if ! openstack server delete $instance; then + echo "Failed to delete $status instance $instance" + result=1 + fi + done + done + exit $result + env: + OS_CLOUD: openstack + OS_APPLICATION_CREDENTIAL_ID: ${{ secrets.OS_APPLICATION_CREDENTIAL_ID }} + OS_APPLICATION_CREDENTIAL_SECRET: ${{ secrets.OS_APPLICATION_CREDENTIAL_SECRET }} From c338dd9b7cad77c14eb15eb0193d02b0c9ff78b4 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Wed, 14 Feb 2024 15:52:55 +0000 Subject: [PATCH 017/128] CI: Use skc-ci-aio user for aio jobs This user only has read-only access to the package and container repositories, so is safer than using the release-train-ci user which has read/write permissions. For the container image build job we can use the skc-ci-aio user to access the package repositories, but must use the release-train-ci user to push container images. --- .../environments/ci-aio/stackhpc-ci.yml | 22 +++++++++---------- .../environments/ci-builder/stackhpc-ci.yml | 10 +++++++-- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/etc/kayobe/environments/ci-aio/stackhpc-ci.yml b/etc/kayobe/environments/ci-aio/stackhpc-ci.yml index 6050bffd2..c41435c55 100644 --- a/etc/kayobe/environments/ci-aio/stackhpc-ci.yml +++ b/etc/kayobe/environments/ci-aio/stackhpc-ci.yml @@ -19,8 +19,14 @@ resolv_is_managed: false # Build and deploy the development Pulp service repositories. # Use Ark's package repositories to install packages. stackhpc_repo_mirror_url: "{{ stackhpc_release_pulp_url }}" -stackhpc_repo_mirror_username: "{{ stackhpc_docker_registry_username }}" -stackhpc_repo_mirror_password: "{{ stackhpc_docker_registry_password }}" +stackhpc_repo_mirror_username: "skc-ci-aio" +stackhpc_repo_mirror_password: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 31386366383365666135336331663635396237623139306362633933636233613765663731666338 + 3633633736333936383439623066653663333964343234350a393137383537316164323837386437 + 36613139323161643766666565643739373037623363636234343965343436653261326238393566 + 3837336661653962340a316631366463623138623530373133336665376433633437306631383666 + 30333461333535363433363336663664316634343432633766346564323833346663 # Build and deploy released Pulp repository versions. stackhpc_repo_centos_stream_baseos_version: "{{ stackhpc_pulp_repo_centos_stream_8_baseos_version }}" @@ -75,13 +81,5 @@ stackhpc_include_os_minor_version_in_repo_url: true # Host and port of container registry. # Push built images to the development Pulp service registry. stackhpc_docker_registry: "{{ stackhpc_repo_mirror_url | regex_replace('^https?://', '') }}" - -# Username and password of container registry. -stackhpc_docker_registry_username: "release-train-ci" -stackhpc_docker_registry_password: !vault | - $ANSIBLE_VAULT;1.1;AES256 - 38356134376436656165303634626531653836366233383531343439646433376334396438373735 - 3135643664353934356237376134623235356137383263300a333165386562396134633534376532 - 34386133383366326639353432386235336132663839333337323739633434613934346462363031 - 3265323831663964360a643962346231386462323236373963633066393736323234303833363535 - 3664 +stackhpc_docker_registry_username: "{{ stackhpc_repo_mirror_username }}" +stackhpc_docker_registry_password: "{{ stackhpc_repo_mirror_password }}" diff --git a/etc/kayobe/environments/ci-builder/stackhpc-ci.yml b/etc/kayobe/environments/ci-builder/stackhpc-ci.yml index f31629357..e17ccf69c 100644 --- a/etc/kayobe/environments/ci-builder/stackhpc-ci.yml +++ b/etc/kayobe/environments/ci-builder/stackhpc-ci.yml @@ -44,8 +44,14 @@ resolv_is_managed: false # Build against the development Pulp service repositories. # Use Ark's package repositories to install packages. stackhpc_repo_mirror_url: "{{ stackhpc_repo_mirror_auth_proxy_url if stackhpc_repo_mirror_auth_proxy_enabled | bool else stackhpc_release_pulp_url }}" -stackhpc_repo_mirror_username: "{{ stackhpc_docker_registry_username }}" -stackhpc_repo_mirror_password: "{{ stackhpc_docker_registry_password }}" +stackhpc_repo_mirror_username: "skc-ci-aio" +stackhpc_repo_mirror_password: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 31386366383365666135336331663635396237623139306362633933636233613765663731666338 + 3633633736333936383439623066653663333964343234350a393137383537316164323837386437 + 36613139323161643766666565643739373037623363636234343965343436653261326238393566 + 3837336661653962340a316631366463623138623530373133336665376433633437306631383666 + 30333461333535363433363336663664316634343432633766346564323833346663 # Build against released Pulp repository versions. stackhpc_repo_centos_stream_baseos_version: "{{ stackhpc_pulp_repo_centos_stream_8_baseos_version }}" From 3818ff9655fd48dfa529b6f8708bda03b0ce6fb0 Mon Sep 17 00:00:00 2001 From: Seunghun Lee <45145778+seunghun1ee@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:16:46 +0000 Subject: [PATCH 018/128] Ubuntu Focal to Jammy migration support (#902) Add playbook to upgrade ubuntu focal hosts Co-authored-by: Alex-Welsh --- etc/kayobe/ansible/ubuntu-upgrade.yml | 110 ++++++++++++++++++ ...tu-migration-support-2d7e8a6e1a2103cc.yaml | 4 + tools/ubuntu-upgrade-infra-vm.sh | 34 ++++++ tools/ubuntu-upgrade-overcloud.sh | 34 ++++++ tools/ubuntu-upgrade-seed-hypervisor.sh | 29 +++++ tools/ubuntu-upgrade-seed.sh | 29 +++++ 6 files changed, 240 insertions(+) create mode 100644 etc/kayobe/ansible/ubuntu-upgrade.yml create mode 100644 releasenotes/notes/add-automated-ubuntu-migration-support-2d7e8a6e1a2103cc.yaml create mode 100755 tools/ubuntu-upgrade-infra-vm.sh create mode 100755 tools/ubuntu-upgrade-overcloud.sh create mode 100755 tools/ubuntu-upgrade-seed-hypervisor.sh create mode 100755 tools/ubuntu-upgrade-seed.sh diff --git a/etc/kayobe/ansible/ubuntu-upgrade.yml b/etc/kayobe/ansible/ubuntu-upgrade.yml new file mode 100644 index 000000000..3b477731c --- /dev/null +++ b/etc/kayobe/ansible/ubuntu-upgrade.yml @@ -0,0 +1,110 @@ +--- +# To prevent Ansible role dependency errors, this playbook requires that environment variable +# ANSIBLE_ROLES_PATH is defined and includes '$KAYOBE_PATH/ansible/roles' on the Ansible control host. +- name: Migrate hosts from Ubuntu Focal 20.04 to Jammy 22.04 + hosts: overcloud:infra-vms:seed:seed-hypervisor + vars: + ansible_python_interpreter: /usr/bin/python3 + tasks: + - name: Assert that hosts are running Ubuntu Focal + assert: + that: + - ansible_facts.distribution == 'Ubuntu' + - ansible_facts.distribution_major_version == '20' + - ansible_facts.distribution_release == 'focal' + - os_distribution == 'ubuntu' + fail_msg: >- + This playbook is only designed for Ubuntu Focal 20.04 hosts. Ensure + that you are limiting it to only run on Focal hosts and + os_distribution is set to ubuntu. + + - name: Ensure apt packages are up to date + apt: + update_cache: true + upgrade: yes + become: true + + - name: Ensure do-release-upgrade is installed + package: + name: ubuntu-release-upgrader-core + state: latest + become: true + + - name: Check whether a reboot is required + stat: + path: /var/run/reboot-required + register: file_status + + - name: Reboot to apply updates + reboot: + reboot_timeout: 1200 + connect_timeout: 600 + become: true + when: file_status.stat.exists + + # NOTE: We cannot use apt_repository here because definitions must exist within the standard repos.list + - name: Ensure Jammy repo definitions exist in sources.list + blockinfile: + path: /etc/apt/sources.list + block: | + deb {{ stackhpc_repo_ubuntu_jammy_url }} jammy main restricted universe multiverse + deb {{ stackhpc_repo_ubuntu_jammy_url }} jammy-updates main restricted universe multiverse + deb {{ stackhpc_repo_ubuntu_jammy_url }} jammy-backports main restricted universe multiverse + deb {{ stackhpc_repo_ubuntu_jammy_security_url }} jammy-security main restricted universe multiverse + become: true + + - name: Do release upgrade + command: do-release-upgrade -f DistUpgradeViewNonInteractive + become: true + + - name: Ensure old venvs do not exist + file: + path: "/opt/kayobe/venvs/{{ item }}" + state: absent + loop: + - kayobe + - kolla-ansible + become: true + + - name: Update Python and current user facts before re-creating Kayobe venv + ansible.builtin.setup: + filter: "{{ kayobe_ansible_setup_filter }}" + gather_subset: "{{ kayobe_ansible_setup_gather_subset }}" + +- name: Run the Kayobe kayobe-target-venv playbook to ensure kayobe venv exists on remote host + import_playbook: "{{ lookup('ansible.builtin.env', 'VIRTUAL_ENV') }}/share/kayobe/ansible/kayobe-target-venv.yml" + +- name: Run the Kayobe network configuration playbook, to ensure definitions are not lost on reboot + import_playbook: "{{ lookup('ansible.builtin.env', 'VIRTUAL_ENV') }}/share/kayobe/ansible/network.yml" + +- name: Reboot and confirm the host is upgraded to Jammy 22.04 + hosts: overcloud:infra-vms:seed:seed-hypervisor + vars: + ansible_python_interpreter: /usr/bin/python3 + tasks: + - name: Ensure Jammy repo definitions do not exist in sources.list + blockinfile: + path: /etc/apt/sources.list + state: absent + become: true + + - name: Reboot and wait + reboot: + reboot_timeout: 1200 + connect_timeout: 600 + become: true + + - name: Update distribution facts + ansible.builtin.setup: + filter: "{{ kayobe_ansible_setup_filter }}" + gather_subset: "{{ kayobe_ansible_setup_gather_subset }}" + + - name: Assert that hosts are now using Ubuntu 22 + assert: + that: + - ansible_facts.distribution_major_version == '22' + - ansible_facts.distribution_release == 'jammy' + +- name: Run the OVN chassis priority fix playbook + import_playbook: "{{ lookup('ansible.builtin.env', 'KAYOBE_CONFIG_PATH') }}/ansible/ovn-fix-chassis-priorities.yml" + when: kolla_enable_ovn diff --git a/releasenotes/notes/add-automated-ubuntu-migration-support-2d7e8a6e1a2103cc.yaml b/releasenotes/notes/add-automated-ubuntu-migration-support-2d7e8a6e1a2103cc.yaml new file mode 100644 index 000000000..89216342d --- /dev/null +++ b/releasenotes/notes/add-automated-ubuntu-migration-support-2d7e8a6e1a2103cc.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Add support for automated Ubuntu Focal to Jammy migration. diff --git a/tools/ubuntu-upgrade-infra-vm.sh b/tools/ubuntu-upgrade-infra-vm.sh new file mode 100755 index 000000000..8d5810174 --- /dev/null +++ b/tools/ubuntu-upgrade-infra-vm.sh @@ -0,0 +1,34 @@ +#! /usr/bin/bash + +set -e + +if [[ ! $1 ]]; then + echo "Usage: infra-vm-ubuntu-upgrade.sh " + exit 2 +fi + +if [[ ! $KAYOBE_PATH ]]; then + echo "Environment variable \$KAYOBE_PATH is not defined" + exit 2 +fi + +if [[ ! $KAYOBE_CONFIG_PATH ]]; then + echo "Environment variable \$KAYOBE_CONFIG_PATH is not defined" + exit 2 +fi + +if [[ ! $ANSIBLE_ROLES_PATH ]]; then + set -x + export ANSIBLE_ROLES_PATH=$KAYOBE_PATH/ansible/roles + set +x +else + set -x + export ANSIBLE_ROLES_PATH=$ANSIBLE_ROLES_PATH:$KAYOBE_PATH/ansible/roles + set +x +fi + +set -x + +kayobe playbook run $KAYOBE_CONFIG_PATH/ansible/ubuntu-upgrade.yml -e os_release=jammy --limit $1 + +kayobe infra vm host configure --limit $1 -e os_release=jammy diff --git a/tools/ubuntu-upgrade-overcloud.sh b/tools/ubuntu-upgrade-overcloud.sh new file mode 100755 index 000000000..3e351d6d6 --- /dev/null +++ b/tools/ubuntu-upgrade-overcloud.sh @@ -0,0 +1,34 @@ +#! /usr/bin/bash + +set -e + +if [[ ! $1 ]]; then + echo "Usage: overcloud-ubuntu-upgrade.sh " + exit 2 +fi + +if [[ ! $KAYOBE_PATH ]]; then + echo "Environment variable \$KAYOBE_PATH is not defined" + exit 2 +fi + +if [[ ! $KAYOBE_CONFIG_PATH ]]; then + echo "Environment variable \$KAYOBE_CONFIG_PATH is not defined" + exit 2 +fi + +if [[ ! $ANSIBLE_ROLES_PATH ]]; then + set -x + export ANSIBLE_ROLES_PATH=$KAYOBE_PATH/ansible/roles + set +x +else + set -x + export ANSIBLE_ROLES_PATH=$ANSIBLE_ROLES_PATH:$KAYOBE_PATH/ansible/roles + set +x +fi + +set -x + +kayobe playbook run $KAYOBE_CONFIG_PATH/ansible/ubuntu-upgrade.yml -e os_release=jammy --limit $1 + +kayobe overcloud host configure --limit $1 --kolla-limit $1 -e os_release=jammy diff --git a/tools/ubuntu-upgrade-seed-hypervisor.sh b/tools/ubuntu-upgrade-seed-hypervisor.sh new file mode 100755 index 000000000..ad09f2b34 --- /dev/null +++ b/tools/ubuntu-upgrade-seed-hypervisor.sh @@ -0,0 +1,29 @@ +#! /usr/bin/bash + +set -e + +if [[ ! $KAYOBE_PATH ]]; then + echo "Environment variable \$KAYOBE_PATH is not defined" + exit 2 +fi + +if [[ ! $KAYOBE_CONFIG_PATH ]]; then + echo "Environment variable \$KAYOBE_CONFIG_PATH is not defined" + exit 2 +fi + +if [[ ! $ANSIBLE_ROLES_PATH ]]; then + set -x + export ANSIBLE_ROLES_PATH=$KAYOBE_PATH/ansible/roles + set +x +else + set -x + export ANSIBLE_ROLES_PATH=$ANSIBLE_ROLES_PATH:$KAYOBE_PATH/ansible/roles + set +x +fi + +set -x + +kayobe playbook run $KAYOBE_CONFIG_PATH/ansible/ubuntu-upgrade.yml -e os_release=jammy --limit seed-hypervisor + +kayobe seed hypervisor host configure diff --git a/tools/ubuntu-upgrade-seed.sh b/tools/ubuntu-upgrade-seed.sh new file mode 100755 index 000000000..4a48d5f36 --- /dev/null +++ b/tools/ubuntu-upgrade-seed.sh @@ -0,0 +1,29 @@ +#! /usr/bin/bash + +set -e + +if [[ ! $KAYOBE_PATH ]]; then + echo "Environment variable \$KAYOBE_PATH is not defined" + exit 2 +fi + +if [[ ! $KAYOBE_CONFIG_PATH ]]; then + echo "Environment variable \$KAYOBE_CONFIG_PATH is not defined" + exit 2 +fi + +if [[ ! $ANSIBLE_ROLES_PATH ]]; then + set -x + export ANSIBLE_ROLES_PATH=$KAYOBE_PATH/ansible/roles + set +x +else + set -x + export ANSIBLE_ROLES_PATH=$ANSIBLE_ROLES_PATH:$KAYOBE_PATH/ansible/roles + set +x +fi + +set -x + +kayobe playbook run $KAYOBE_CONFIG_PATH/ansible/ubuntu-upgrade.yml -e os_release=jammy --limit seed + +kayobe seed host configure From 55672dfdd8e440876873999485e0f10d1ee4cc1b Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Fri, 16 Feb 2024 14:46:58 +0100 Subject: [PATCH 019/128] Stop warning about invalid group name characters This should silence the following Ansible warning [1]: [WARNING]: Invalid characters were found in group names but not replaced, use -vvvv to see details [1] https://docs.ansible.com/ansible/latest/reference_appendices/config.html#transform-invalid-group-chars --- etc/kayobe/ansible.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/etc/kayobe/ansible.cfg b/etc/kayobe/ansible.cfg index 515af8d32..901001ad8 100644 --- a/etc/kayobe/ansible.cfg +++ b/etc/kayobe/ansible.cfg @@ -8,6 +8,8 @@ bin_ansible_callbacks = True inject_facts_as_vars = False # Add timing information to output callbacks_enabled = ansible.posix.profile_tasks +# Silence warning about invalid characters found in group names +force_valid_group_names = ignore [ssh_connection] pipelining = True From f2ba8ec2a10cf8c2cb99a8a0a4c73b4c3d5ded09 Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Sat, 17 Feb 2024 08:54:29 +0100 Subject: [PATCH 020/128] Fix growroot playbook for NVMe devices The disk_tmp variable uses a device path rather than a device name. --- etc/kayobe/ansible/growroot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/kayobe/ansible/growroot.yml b/etc/kayobe/ansible/growroot.yml index cdd7293d9..aa3dcc05a 100644 --- a/etc/kayobe/ansible/growroot.yml +++ b/etc/kayobe/ansible/growroot.yml @@ -77,7 +77,7 @@ vars: pv: "{{ pvs.stdout | from_json }}" disk_tmp: "{{ pv.report[0].pv[0].pv_name[:-1] }}" - disk: "{{ disk_tmp[:-1] if disk_tmp[-1] == 'p' and disk_tmp[:4] == 'nvme' else disk_tmp }}" + disk: "{{ disk_tmp[:-1] if disk_tmp[-1] == 'p' and disk_tmp[:9] == '/dev/nvme' else disk_tmp }}" part_num: "{{ pv.report[0].pv[0].pv_name[-1] }}" become: true failed_when: "growpart.rc != 0 and 'NOCHANGE' not in growpart.stdout" From ca7fd1d17ef20cabc2f990e7fa09aae0d2de82d4 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Mon, 19 Feb 2024 12:32:35 +0000 Subject: [PATCH 021/128] CI: Clean up instances in BUILD state, list by ID --- .github/workflows/stackhpc-ci-cleanup.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stackhpc-ci-cleanup.yml b/.github/workflows/stackhpc-ci-cleanup.yml index 672500ab1..df032fdac 100644 --- a/.github/workflows/stackhpc-ci-cleanup.yml +++ b/.github/workflows/stackhpc-ci-cleanup.yml @@ -40,8 +40,8 @@ jobs: run: | result=0 changes_before=$(date -Imin -d -3hours) - for status in ACTIVE ERROR SHUTOFF; do - for instance in $(openstack server list --tags skc-ci-aio --os-compute-api-version 2.66 --format value --column Name --changes-before $changes_before --status $status); do + for status in ACTIVE BUILD ERROR SHUTOFF; do + for instance in $(openstack server list --tags skc-ci-aio --os-compute-api-version 2.66 --format value --column ID --changes-before $changes_before --status $status); do echo "Cleaning up $status instance $instance" openstack server show $instance if ! openstack server delete $instance; then From 186a03c98b7e66ee6d8670234fbc3472e3d88db4 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Mon, 19 Feb 2024 12:33:25 +0000 Subject: [PATCH 022/128] CI: Use correct URL for upper constraints This URL should not be affected by branch names etc. --- .github/workflows/overcloud-host-image-build.yml | 2 +- .github/workflows/overcloud-host-image-upload.yml | 2 +- .github/workflows/stackhpc-ci-cleanup.yml | 2 +- etc/kayobe/environments/ci-builder/inventory/hosts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/overcloud-host-image-build.yml b/.github/workflows/overcloud-host-image-build.yml index 0c2fc3efb..cbccca23e 100644 --- a/.github/workflows/overcloud-host-image-build.yml +++ b/.github/workflows/overcloud-host-image-build.yml @@ -139,7 +139,7 @@ jobs: - name: Install OpenStack client run: | source venvs/kayobe/bin/activate && - pip install python-openstackclient -c https://opendev.org/openstack/requirements/raw/branch/stable/${{ steps.openstack_release.outputs.openstack_release }}/upper-constraints.txt + pip install python-openstackclient -c https://releases.openstack.org/constraints/upper/${{ steps.openstack_release.outputs.openstack_release }} - name: Build a CentOS Stream 8 overcloud host image id: build_centos_stream_8 diff --git a/.github/workflows/overcloud-host-image-upload.yml b/.github/workflows/overcloud-host-image-upload.yml index f18f8ef4c..633f423b5 100644 --- a/.github/workflows/overcloud-host-image-upload.yml +++ b/.github/workflows/overcloud-host-image-upload.yml @@ -92,7 +92,7 @@ jobs: - name: Install OpenStack client run: | source venvs/kayobe/bin/activate && - pip install python-openstackclient -c https://opendev.org/openstack/requirements/raw/branch/stable/yoga/upper-constraints.txt + pip install python-openstackclient -c https://releases.openstack.org/constraints/upper/${{ steps.openstack_release.outputs.openstack_release }} - name: Output CentOS Stream 8 image tag id: centos_8_stream_image_tag diff --git a/.github/workflows/stackhpc-ci-cleanup.yml b/.github/workflows/stackhpc-ci-cleanup.yml index df032fdac..d0da0c051 100644 --- a/.github/workflows/stackhpc-ci-cleanup.yml +++ b/.github/workflows/stackhpc-ci-cleanup.yml @@ -34,7 +34,7 @@ jobs: - name: Install OpenStack client run: | - pip install python-openstackclient -c https://opendev.org/openstack/requirements/raw/branch/stable/${{ steps.openstack_release.outputs.openstack_release }}/upper-constraints.txt + pip install python-openstackclient -c https://releases.openstack.org/constraints/upper/${{ steps.openstack_release.outputs.openstack_release }} - name: Clean up aio instances over 3 hours old run: | diff --git a/etc/kayobe/environments/ci-builder/inventory/hosts b/etc/kayobe/environments/ci-builder/inventory/hosts index 33fda8b73..49b7be166 100644 --- a/etc/kayobe/environments/ci-builder/inventory/hosts +++ b/etc/kayobe/environments/ci-builder/inventory/hosts @@ -1,3 +1,3 @@ # A 'seed' host used for building images. [seed] -builder +localhost ansible_connection=local ansible_python_interpreter=/usr/bin/python3 From 8ddb62df6dfb627351dc503df2542540ad35b96d Mon Sep 17 00:00:00 2001 From: Grzegorz Bialas Date: Mon, 19 Feb 2024 16:52:06 +0100 Subject: [PATCH 023/128] bumping ansible vault --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0ca7aab63..2089c4b3f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ kayobe@git+https://github.com/stackhpc/kayobe@stackhpc/yoga ansible-modules-hashivault@git+https://github.com/stackhpc/ansible-modules-hashivault@stackhpc;python_version < "3.8" -ansible-modules-hashivault;python_version >= "3.8" +ansible-modules-hashivault@git+https://github.com/stackhpc/ansible-modules-hashivault@stackhpc-py39;python_version >= "3.8" jmespath From 0030f3edcdcdd4e8b22a25ae33833f65bc2521c1 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Tue, 20 Feb 2024 12:13:33 +0000 Subject: [PATCH 024/128] docs: Update network interface note to mention group_vars --- doc/source/operations/rocky-linux-9.rst | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/doc/source/operations/rocky-linux-9.rst b/doc/source/operations/rocky-linux-9.rst index d83497caf..b472942a4 100644 --- a/doc/source/operations/rocky-linux-9.rst +++ b/doc/source/operations/rocky-linux-9.rst @@ -245,9 +245,10 @@ Potential issues - Network interface names may change between CentOS Stream 8 and Rocky Linux 9, in which case you will need to update Kayobe configuration. Note that the configuration should remain correct for hosts not yet migrated, otherwise - fact gathering may fail. For example, this can be done using ``host_vars``. - Once all hosts are migrated, the change can be moved to ``group_vars`` and - the temporary ``host_vars`` deleted. + fact gathering may fail. For example, this can be done using ``group_vars`` + with a temporary group for the updated hosts or ``host_vars``. Once all + hosts are migrated, the change can be moved to the original group's + ``group_vars`` and the temporary changes reverted. Full procedure for one host --------------------------- @@ -377,9 +378,10 @@ Potential issues - Network interface names may change between CentOS Stream 8 and Rocky Linux 9, in which case you will need to update Kayobe configuration. Note that the configuration should remain correct for hosts not yet migrated, otherwise - fact gathering may fail. For example, this can be done using ``host_vars``. - Once all hosts are migrated, the change can be moved to ``group_vars`` and - the temporary ``host_vars`` deleted. + fact gathering may fail. For example, this can be done using ``group_vars`` + with a temporary group for the updated hosts or ``host_vars``. Once all + hosts are migrated, the change can be moved to the original group's + ``group_vars`` and the temporary changes reverted. Full procedure for one batch of hosts ------------------------------------- @@ -533,9 +535,10 @@ Potential issues - Network interface names may change between CentOS Stream 8 and Rocky Linux 9, in which case you will need to update Kayobe configuration. Note that the configuration should remain correct for hosts not yet migrated, otherwise - fact gathering may fail. For example, this can be done using ``host_vars``. - Once all hosts are migrated, the change can be moved to ``group_vars`` and - the temporary ``host_vars`` deleted. + fact gathering may fail. For example, this can be done using ``group_vars`` + with a temporary group for the updated hosts or ``host_vars``. Once all + hosts are migrated, the change can be moved to the original group's + ``group_vars`` and the temporary changes reverted. Full procedure for any storage host ----------------------------------- From b3e2c5601698789d6dadf65637b21b5f9c644523 Mon Sep 17 00:00:00 2001 From: "Max.Bed4d" Date: Fri, 9 Feb 2024 15:49:30 +0000 Subject: [PATCH 025/128] Enable AiO jobs to be cancelled even if they're underway. --- .github/workflows/stackhpc-all-in-one.yml | 2 +- .github/workflows/stackhpc-pull-request.yml | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/stackhpc-all-in-one.yml b/.github/workflows/stackhpc-all-in-one.yml index 26de98f66..59931e8cd 100644 --- a/.github/workflows/stackhpc-all-in-one.yml +++ b/.github/workflows/stackhpc-all-in-one.yml @@ -69,7 +69,7 @@ jobs: # NOTE: Runner needs unzip and nodejs packages. all-in-one: name: All in one - if: inputs.if + if: ${{ inputs.if && !cancelled() }} runs-on: arc-skc-aio-runner permissions: {} env: diff --git a/.github/workflows/stackhpc-pull-request.yml b/.github/workflows/stackhpc-pull-request.yml index 987e15356..ce9243ba2 100644 --- a/.github/workflows/stackhpc-pull-request.yml +++ b/.github/workflows/stackhpc-pull-request.yml @@ -88,7 +88,7 @@ jobs: OS_CLOUD: openstack if: ${{ needs.check-changes.outputs.aio == 'true' }} secrets: inherit - if: ${{ ! failure() && github.repository == 'stackhpc/stackhpc-kayobe-config' }} + if: ${{ ! failure() && ! cancelled() && github.repository == 'stackhpc/stackhpc-kayobe-config' }} all-in-one-centos-ovn: name: aio (CentOS OVN) @@ -102,7 +102,7 @@ jobs: OS_CLOUD: openstack if: ${{ needs.check-changes.outputs.aio == 'true' }} secrets: inherit - if: ${{ ! failure() && github.repository == 'stackhpc/stackhpc-kayobe-config' }} + if: ${{ ! failure() && ! cancelled() && github.repository == 'stackhpc/stackhpc-kayobe-config' }} all-in-one-rocky-8-ovs: name: aio (Rocky OVS) @@ -118,7 +118,7 @@ jobs: OS_CLOUD: openstack if: ${{ needs.check-changes.outputs.aio == 'true' }} secrets: inherit - if: ${{ ! failure() && github.repository == 'stackhpc/stackhpc-kayobe-config' }} + if: ${{ ! failure() && ! cancelled() && github.repository == 'stackhpc/stackhpc-kayobe-config' }} all-in-one-rocky-8-ovn: name: aio (Rocky OVN) @@ -134,7 +134,7 @@ jobs: OS_CLOUD: openstack if: ${{ needs.check-changes.outputs.aio == 'true' }} secrets: inherit - if: ${{ ! failure() && github.repository == 'stackhpc/stackhpc-kayobe-config' }} + if: ${{ ! failure() && ! cancelled() && github.repository == 'stackhpc/stackhpc-kayobe-config' }} all-in-one-ubuntu-ovs: name: aio (Ubuntu OVS) @@ -151,7 +151,7 @@ jobs: OS_CLOUD: openstack if: ${{ needs.check-changes.outputs.aio == 'true' }} secrets: inherit - if: ${{ ! failure() && github.repository == 'stackhpc/stackhpc-kayobe-config' }} + if: ${{ ! failure() && ! cancelled() && github.repository == 'stackhpc/stackhpc-kayobe-config' }} all-in-one-ubuntu-ovn: name: aio (Ubuntu OVN) @@ -168,7 +168,7 @@ jobs: OS_CLOUD: openstack if: ${{ needs.check-changes.outputs.aio == 'true' }} secrets: inherit - if: ${{ ! failure() && github.repository == 'stackhpc/stackhpc-kayobe-config' }} + if: ${{ ! failure() && ! cancelled() && github.repository == 'stackhpc/stackhpc-kayobe-config' }} all-in-one-ubuntu-jammy-ovs: name: aio (Ubuntu Jammy OVS) @@ -185,7 +185,7 @@ jobs: OS_CLOUD: openstack if: ${{ needs.check-changes.outputs.aio == 'true' }} secrets: inherit - if: ${{ ! failure() && github.repository == 'stackhpc/stackhpc-kayobe-config' }} + if: ${{ ! failure() && ! cancelled() && github.repository == 'stackhpc/stackhpc-kayobe-config' }} all-in-one-ubuntu-jammy-ovn: name: aio (Ubuntu Jammy OVN) @@ -202,7 +202,7 @@ jobs: OS_CLOUD: openstack if: ${{ needs.check-changes.outputs.aio == 'true' }} secrets: inherit - if: ${{ ! failure() && github.repository == 'stackhpc/stackhpc-kayobe-config' }} + if: ${{ ! failure() && ! cancelled() && github.repository == 'stackhpc/stackhpc-kayobe-config' }} all-in-one-rocky-9-ovs: name: aio (Rocky 9 OVS) @@ -219,7 +219,7 @@ jobs: OS_CLOUD: openstack if: ${{ needs.check-changes.outputs.aio == 'true' }} secrets: inherit - if: ${{ ! failure() && github.repository == 'stackhpc/stackhpc-kayobe-config' }} + if: ${{ ! failure() && ! cancelled() && github.repository == 'stackhpc/stackhpc-kayobe-config' }} all-in-one-rocky-9-ovn: name: aio (Rocky 9 OVN) @@ -236,4 +236,4 @@ jobs: OS_CLOUD: openstack if: ${{ needs.check-changes.outputs.aio == 'true' }} secrets: inherit - if: ${{ ! failure() && github.repository == 'stackhpc/stackhpc-kayobe-config' }} + if: ${{ ! failure() && ! cancelled() && github.repository == 'stackhpc/stackhpc-kayobe-config' }} From 2ce47a0129027950088016526399dd8a14d00e6c Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Tue, 20 Feb 2024 14:47:10 +0000 Subject: [PATCH 026/128] CI: Add cancellation support to check-tags job --- .github/workflows/stackhpc-check-tags.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stackhpc-check-tags.yml b/.github/workflows/stackhpc-check-tags.yml index f5a12a714..4016c00e9 100644 --- a/.github/workflows/stackhpc-check-tags.yml +++ b/.github/workflows/stackhpc-check-tags.yml @@ -23,7 +23,7 @@ env: jobs: check-tags: name: Check container image tags - if: inputs.if + if: ${{ inputs.if && ! cancelled() }} runs-on: arc-skc-aio-runner permissions: {} env: From 6ca2eb651472672bfd02e931cf05f05ef2af8218 Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Tue, 20 Feb 2024 15:06:46 +0000 Subject: [PATCH 027/128] Adds alerts for software raid failures (#935) * Adds alerts for software raid failures See: https://github.com/prometheus/node_exporter/blob/master/docs/node-mixin/alerts/alerts.libsonnet * Fix typo in release notes --------- Co-authored-by: Doug Szumski --- .../kolla/config/prometheus/system.rules | 20 +++++++++++++++++++ .../adds-mdraid-alerts-309fb79e61389325.yaml | 4 ++++ 2 files changed, 24 insertions(+) create mode 100644 releasenotes/notes/adds-mdraid-alerts-309fb79e61389325.yaml diff --git a/etc/kayobe/kolla/config/prometheus/system.rules b/etc/kayobe/kolla/config/prometheus/system.rules index c82bed16e..c2caa9898 100644 --- a/etc/kayobe/kolla/config/prometheus/system.rules +++ b/etc/kayobe/kolla/config/prometheus/system.rules @@ -104,4 +104,24 @@ groups: annotations: summary: Host conntrack limit (instance {{ $labels.instance }}) description: "The number of conntrack is approaching limit" + + - alert: NodeRAIDDegraded + expr: | + node_md_disks_required{job="node",device!=""} - ignoring (state) (node_md_disks{state="active",job="node",device!=""}) > 0 + for: "15m" + labels: + severity: critical + annotations: + description: "RAID array '{{ $labels.device }}' at {{ $labels.instance }} is in degraded state due to one or more disks failures. Number of spare drives is insufficient to fix issue automatically." + summary: "RAID Array is degraded." + + - alert: NodeRAIDDiskFailure + expr: | + node_md_disks{state="failed",job="node",device!=""} > 0 + labels: + severity: warning + annotations: + description: "At least one device in RAID array at {{ $labels.instance }} failed. Array '{{ $labels.device }}' needs attention and possibly a disk swap." + summary: "Failed device in RAID array." + {% endraw %} diff --git a/releasenotes/notes/adds-mdraid-alerts-309fb79e61389325.yaml b/releasenotes/notes/adds-mdraid-alerts-309fb79e61389325.yaml new file mode 100644 index 000000000..831281609 --- /dev/null +++ b/releasenotes/notes/adds-mdraid-alerts-309fb79e61389325.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Adds alerts for software raid failures. From 880d0b2a5ec7acd543c7bade60dae7be47f89a87 Mon Sep 17 00:00:00 2001 From: Bartosz Bezak Date: Mon, 19 Feb 2024 15:35:50 +0100 Subject: [PATCH 028/128] docs: guide for migrating to containerized libvirt in R8/R9 migration --- doc/source/operations/rocky-linux-9.rst | 30 ++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/doc/source/operations/rocky-linux-9.rst b/doc/source/operations/rocky-linux-9.rst index 5b13c2807..b174d11e7 100644 --- a/doc/source/operations/rocky-linux-9.rst +++ b/doc/source/operations/rocky-linux-9.rst @@ -429,13 +429,33 @@ Full procedure for one batch of hosts kayobe overcloud provision -l -5. Host configure: +5. If the compute node is using Libvirt on the Host, and one wants to transition to containerized Libvirt. + + 1. Update kolla.yml + + .. code-block:: yaml + + kolla_enable_nova_libvirt_container: "{{ inventory_hostname != 'localhost' and ansible_facts.distribution_major_version == '9' }}" + + 2. Update kolla/globals.yml + + .. code-block:: yaml + + enable_nova_libvirt_container: "{% raw %}{{ ansible_facts.distribution_major_version == '9' }}{% endraw %}" + + .. note:: + + Those settings are needed only for the timeframe of migration to Rocky Linux 9, + when CentOS Stream 8 or Rocky Linux 8 hosts with Libvirt on the hosts exists + in the environment. + +6. Host configure: .. code:: console kayobe overcloud host configure -l -kl -6. If the compute node is running Ceph OSD services: +7. If the compute node is running Ceph OSD services: 1. Make sure the cephadm public key is in ``authorized_keys`` for stack or root user - depends on your setup. For example, your SSH key may @@ -460,13 +480,13 @@ Full procedure for one batch of hosts ceph -s ceph -w -7. Service deploy: +8. Service deploy: .. code:: console kayobe overcloud service deploy -kl -8. If you are using Wazuh, you will need to deploy the agent again. +9. If you are using Wazuh, you will need to deploy the agent again. Note that CIS benchmarks do not run on RL9 out-the-box. See `our Wazuh docs `__ for details. @@ -475,7 +495,7 @@ Full procedure for one batch of hosts kayobe playbook run $KAYOBE_CONFIG_PATH/ansible/wazuh-agent.yml -l -9. Restore the system to full health. +10. Restore the system to full health. 1. If any VMs were powered off, they may now be powered back on. From dce592ca8c66a867f81a5c3c537f0385d94ba92e Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Wed, 21 Feb 2024 08:59:43 +0000 Subject: [PATCH 029/128] Remove Ubuntu Jammy upgrade release note This feature is useful in Yoga only, so no need to announce it. --- ...d-automated-ubuntu-migration-support-2d7e8a6e1a2103cc.yaml | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 releasenotes/notes/add-automated-ubuntu-migration-support-2d7e8a6e1a2103cc.yaml diff --git a/releasenotes/notes/add-automated-ubuntu-migration-support-2d7e8a6e1a2103cc.yaml b/releasenotes/notes/add-automated-ubuntu-migration-support-2d7e8a6e1a2103cc.yaml deleted file mode 100644 index 89216342d..000000000 --- a/releasenotes/notes/add-automated-ubuntu-migration-support-2d7e8a6e1a2103cc.yaml +++ /dev/null @@ -1,4 +0,0 @@ ---- -features: - - | - Add support for automated Ubuntu Focal to Jammy migration. From 5931ff07b7cf585c87e2d6987ad43137bb60c2d5 Mon Sep 17 00:00:00 2001 From: Jake Hutchinson Date: Mon, 9 Oct 2023 10:23:13 +0100 Subject: [PATCH 030/128] Various os_capacity fixes --- .yamllint | 1 + doc/source/configuration/monitoring.rst | 43 ++- .../ansible/deploy-os-capacity-exporter.yml | 7 + .../templates/os_capacity-clouds.yml.j2 | 11 +- .../openstack/grafana_cloud_dashboard.json | 37 +- .../openstack/grafana_project_dashboard.json | 343 +++++++++++++++--- .../{os_exporter.cfg => os_capacity.cfg} | 6 +- .../prometheus.yml.d/70-oscapacity.yml | 5 +- etc/kayobe/stackhpc-monitoring.yml | 10 + .../notes/os-capacity-94006f03f16583e4.yaml | 19 +- 10 files changed, 408 insertions(+), 74 deletions(-) rename etc/kayobe/kolla/config/haproxy/services.d/{os_exporter.cfg => os_capacity.cfg} (74%) diff --git a/.yamllint b/.yamllint index 96b2b10dd..1c115e29b 100644 --- a/.yamllint +++ b/.yamllint @@ -20,3 +20,4 @@ ignore: | .github/ .gitlab/ .gitlab-ci.yml + etc/kayobe/kolla/config/prometheus/prometheus.yml.d/70-oscapacity.yml diff --git a/doc/source/configuration/monitoring.rst b/doc/source/configuration/monitoring.rst index 880ca0032..819da9769 100644 --- a/doc/source/configuration/monitoring.rst +++ b/doc/source/configuration/monitoring.rst @@ -140,33 +140,58 @@ enable the ceph mgr exporter. OpenStack Capacity ================== -OpenStack Capacity allows you to see how much space you have avaliable -in your cloud. StackHPC Kayobe Config includes this exporter by default -and it's necessary that some variables are set to allow deployment. +OpenStack Capacity allows you to see how much space you have available +in your cloud. StackHPC Kayobe Config includes a playbook for manual +deployment, and it's necessary that some variables are set before +running this playbook. To successfully deploy OpenStack Capacity, you are required to specify the OpenStack application credentials in ``kayobe/secrets.yml`` as: .. code-block:: yaml - secrets_os_exporter_auth_url: - secrets_os_exporter_credential_id: - secrets_os_exporter_credential_secret: + secrets_os_capacity_credential_id: + secrets_os_capacity_credential_secret: -After defining your credentials, You may deploy OpenStack Capacity +The Keystone authentication URL and OpenStack region can be changed +from their defaults in ``stackhpc-monitoring.yml`` should you need to +set a different OpenStack region for your cloud. The authentication +URL is set to use ``kolla_internal_fqdn`` by default: + +.. code-block:: yaml + + stackhpc_os_capacity_auth_url: + stackhpc_os_capacity_openstack_region_name: + +Additionally, you are required to enable a conditional flag to allow +HAProxy and Prometheus configuration to be templated during deployment. + +.. code-block:: yaml + + stackhpc_enable_os_capacity: true + +If you are deploying in a cloud with internal TLS, you may be required +to disable certificate verification for the OpenStack Capacity exporter +if your certificate is not signed by a trusted CA. + +.. code-block:: yaml + + stackhpc_os_capacity_openstack_verify: false + +After defining your credentials, you may deploy OpenStack Capacity using the ``ansible/deploy-os-capacity-exporter.yml`` Ansible playbook via Kayobe. .. code-block:: console - kayobe playbook run ansible/deploy-os-capacity-exporter.yml + kayobe playbook run $KAYOBE_CONFIG_PATH/ansible/deploy-os-capacity-exporter.yml It is required that you re-configure the Prometheus, Grafana and HAProxy services following deployment, to do this run the following Kayobe command. .. code-block:: console - kayobe overcloud service reconfigure -kt grafana,prometheus,haproxy + kayobe overcloud service reconfigure -kt grafana,prometheus,loadbalancer If you notice ``HaproxyServerDown`` or ``HaproxyBackendDown`` prometheus alerts after deployment it's likely the os_exporter secrets have not been diff --git a/etc/kayobe/ansible/deploy-os-capacity-exporter.yml b/etc/kayobe/ansible/deploy-os-capacity-exporter.yml index 4eeb69431..e5cde5676 100644 --- a/etc/kayobe/ansible/deploy-os-capacity-exporter.yml +++ b/etc/kayobe/ansible/deploy-os-capacity-exporter.yml @@ -3,6 +3,13 @@ gather_facts: false tasks: + - name: Ensure legacy os_exporter.cfg config file is deleted + ansible.builtin.file: + path: /etc/kolla/haproxy/services.d/os_exporter.cfg + state: absent + delegate_to: network + become: true + - name: Create os-capacity directory ansible.builtin.file: path: /opt/kayobe/os-capacity/ diff --git a/etc/kayobe/ansible/templates/os_capacity-clouds.yml.j2 b/etc/kayobe/ansible/templates/os_capacity-clouds.yml.j2 index 89d66c0bc..a821d6dcb 100644 --- a/etc/kayobe/ansible/templates/os_capacity-clouds.yml.j2 +++ b/etc/kayobe/ansible/templates/os_capacity-clouds.yml.j2 @@ -1,10 +1,13 @@ clouds: openstack: auth: - auth_url: "{{ secrets_os_exporter_auth_url }}" - application_credential_id: "{{ secrets_os_exporter_credential_id }}" - application_credential_secret: "{{ secrets_os_exporter_credential_secret }}" - region_name: "RegionOne" + auth_url: "{{ stackhpc_os_capacity_auth_url }}" + application_credential_id: "{{ secrets_os_capacity_credential_id }}" + application_credential_secret: "{{ secrets_os_capacity_credential_secret }}" + region_name: "{{ stackhpc_os_capacity_openstack_region_name }}" interface: "internal" identity_api_version: 3 auth_type: "v3applicationcredential" +{% if not stackhpc_os_capacity_openstack_verify | bool %} + verify: False +{% endif %} diff --git a/etc/kayobe/kolla/config/grafana/dashboards/openstack/grafana_cloud_dashboard.json b/etc/kayobe/kolla/config/grafana/dashboards/openstack/grafana_cloud_dashboard.json index a777c332e..7bdbdee9f 100644 --- a/etc/kayobe/kolla/config/grafana/dashboards/openstack/grafana_cloud_dashboard.json +++ b/etc/kayobe/kolla/config/grafana/dashboards/openstack/grafana_cloud_dashboard.json @@ -25,7 +25,6 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": 2084495, "links": [], "liveNow": false, "panels": [ @@ -66,7 +65,7 @@ }, "gridPos": { "h": 4, - "w": 2.4, + "w": 4.8, "x": 0, "y": 1 }, @@ -86,7 +85,7 @@ }, "textMode": "auto" }, - "pluginVersion": "9.4.7", + "pluginVersion": "10.1.5", "repeat": "flavors", "repeatDirection": "h", "targets": [ @@ -96,7 +95,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "openstack_free_capacity_by_flavor_total{flavor_name=~\"$flavors\"}", + "expr": "round(avg_over_time(openstack_free_capacity_by_flavor_total{flavor_name=~\"$flavors\"}[30m]), 1)", "legendFormat": "__auto", "range": true, "refId": "A" @@ -424,6 +423,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -465,6 +465,7 @@ "y": 17 }, "id": 5, + "interval": "10m", "options": { "legend": { "calcs": [ @@ -489,7 +490,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "openstack_project_usage{placement_resource=\"MEMORY_MB\"}", + "expr": "avg_over_time(openstack_project_usage{placement_resource=\"MEMORY_MB\"}[30m])", "legendFormat": "{{project_name}}", "range": true, "refId": "A" @@ -522,6 +523,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -552,7 +554,7 @@ } ] }, - "unit": "decmbytes" + "unit": "none" }, "overrides": [] }, @@ -563,6 +565,7 @@ "y": 17 }, "id": 16, + "interval": "10m", "options": { "legend": { "calcs": [ @@ -587,7 +590,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "openstack_project_usage{placement_resource=\"VCPU\"}", + "expr": "avg_over_time(openstack_project_usage{placement_resource=\"VCPU\"}[30m])", "legendFormat": "VCPU {{project_name}}", "range": true, "refId": "A" @@ -598,7 +601,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "openstack_project_usage{placement_resource=\"PCPU\"}", + "expr": "avg_over_time(openstack_project_usage{placement_resource=\"PCPU\"}[30m])", "hide": false, "legendFormat": "PCPU {{project_name}}", "range": true, @@ -646,6 +649,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "smooth", "lineStyle": { "fill": "solid" @@ -689,6 +693,7 @@ "y": 26 }, "id": 6, + "interval": "10m", "options": { "legend": { "calcs": [ @@ -715,15 +720,15 @@ }, "editorMode": "code", "exemplar": false, - "expr": "openstack_free_capacity_hypervisor_by_flavor{flavor_name=~\"$flavors\"}", + "expr": "avg_over_time(openstack_free_capacity_hypervisor_by_flavor{flavor_name=~\"$flavors\"}[30m])", "format": "time_series", "instant": false, "legendFormat": "{{flavor_name}} on {{hypervisor}}", "range": true, - "refId": "Avaliable Capacity on Hypervisors" + "refId": "Available Capacity on Hypervisors" } ], - "title": "Avaliable Capacity for $flavors", + "title": "Available Capacity for $flavors", "type": "timeseries" }, { @@ -750,6 +755,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -791,6 +797,7 @@ "y": 26 }, "id": 4, + "interval": "10m", "options": { "legend": { "calcs": [ @@ -814,8 +821,8 @@ "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, - "editorMode": "builder", - "expr": "openstack_hypervisor_placement_allocatable_capacity{resource=\"MEMORY_MB\"} - on(hypervisor) openstack_hypervisor_placement_allocated{resource=\"MEMORY_MB\"}", + "editorMode": "code", + "expr": "avg_over_time(openstack_hypervisor_placement_allocatable_capacity{resource=\"MEMORY_MB\"}[30m]) - on(hypervisor) avg_over_time(openstack_hypervisor_placement_allocated{resource=\"MEMORY_MB\"}[30m])", "legendFormat": "{{hypervisor}}", "range": true, "refId": "A" @@ -885,7 +892,7 @@ ] }, "time": { - "from": "now-24h", + "from": "now-2d", "to": "now" }, "timepicker": {}, @@ -895,4 +902,4 @@ "version": 1, "weekStart": "" } -{% endraw %} +{% endraw %} \ No newline at end of file diff --git a/etc/kayobe/kolla/config/grafana/dashboards/openstack/grafana_project_dashboard.json b/etc/kayobe/kolla/config/grafana/dashboards/openstack/grafana_project_dashboard.json index c3a483cf9..acb37f195 100644 --- a/etc/kayobe/kolla/config/grafana/dashboards/openstack/grafana_project_dashboard.json +++ b/etc/kayobe/kolla/config/grafana/dashboards/openstack/grafana_project_dashboard.json @@ -25,7 +25,6 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": 2084480, "links": [], "liveNow": false, "panels": [ @@ -89,9 +88,10 @@ "fields": "", "values": false }, - "showUnfilled": true + "showUnfilled": true, + "valueMode": "color" }, - "pluginVersion": "9.4.7", + "pluginVersion": "10.1.5", "targets": [ { "datasource": { @@ -134,6 +134,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -175,6 +176,7 @@ "y": 11 }, "id": 5, + "interval": "10m", "options": { "legend": { "calcs": [ @@ -199,7 +201,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(openstack_project_usage{project_id=~\"${project_id}\"}) by (placement_resource)", + "expr": "sum(avg_over_time(openstack_project_usage{project_id=~\"${project_id}\"}[30m:])) by (placement_resource)", "hide": false, "legendFormat": "{{placement_resource}}", "range": true, @@ -234,6 +236,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -275,6 +278,7 @@ "y": 11 }, "id": 19, + "interval": "10m", "options": { "legend": { "calcs": [ @@ -299,7 +303,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "openstack_project_quota{project_id=~\"${project_id}\"}", + "expr": "avg_over_time(openstack_project_quota{project_id=~\"${project_id}\"}[30m:])", "hide": false, "legendFormat": "{{project_name}}:{{quota_resource}}", "range": true, @@ -333,6 +337,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -433,6 +438,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -474,6 +480,7 @@ "y": 20 }, "id": 20, + "interval": "30m", "options": { "legend": { "calcs": [ @@ -498,7 +505,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(irate(libvirt_domain_vcpu_time_seconds_total{}[5m]) / ignoring(instance,vcpu) group_left(domain) libvirt_domain_info_virtual_cpus{}) by (domain) * on(domain) group_left(instance_name,project_name,project_uuid) libvirt_domain_info_meta{project_uuid=~\"${project_id}\"}", + "expr": "avg(sum(irate(libvirt_domain_vcpu_time_seconds_total{}[5m]) / ignoring(instance,vcpu) group_left(domain) libvirt_domain_info_virtual_cpus{}) by (domain) * on(domain) group_left(instance_name,project_name,project_uuid) libvirt_domain_info_meta{project_uuid=~\"${project_id}\"}) by (project_name)", "hide": false, "legendFormat": "{{instance_name}}", "range": true, @@ -532,6 +539,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -598,7 +606,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "libvirt_domain_memory_stats_used_percent * on(domain) group_left(instance_name,project_name,project_uuid) libvirt_domain_info_meta{project_uuid=~\"${project_id}\"}", + "expr": "avg(libvirt_domain_memory_stats_used_percent * on(domain) group_left(instance_name,project_name,project_uuid) libvirt_domain_info_meta{project_uuid=~\"${project_id}\"}) by (project_name)", "hide": false, "legendFormat": "{{instance_name}}", "range": true, @@ -633,6 +641,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -700,9 +709,9 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "rate(libvirt_domain_block_stats_read_time_seconds_total[5m]) * on(domain) group_left(instance_name,project_name,project_uuid) libvirt_domain_info_meta{project_uuid=~\"${project_id}\"}", + "expr": "avg(rate(libvirt_domain_block_stats_read_time_seconds_total[5m]) * on(domain) group_left(instance_name,project_name,project_uuid) libvirt_domain_info_meta{project_uuid=~\"${project_id}\"}) by (project_name)", "hide": false, - "legendFormat": "{{instance_name}} : read {{target_device}}", + "legendFormat": "read: {{project_name}}", "range": true, "refId": "B" }, @@ -712,9 +721,9 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "rate(libvirt_domain_block_stats_write_time_seconds_total[5m]) * on(domain) group_left(instance_name,project_name,project_uuid) libvirt_domain_info_meta{project_uuid=~\"${project_id}\"} * -1", + "expr": "avg(rate(libvirt_domain_block_stats_write_time_seconds_total[5m]) * on(domain) group_left(instance_name,project_name,project_uuid) libvirt_domain_info_meta{project_uuid=~\"${project_id}\"} * -1) by (project_name)", "hide": false, - "legendFormat": "{{instance_name}} : write {{target_device}}", + "legendFormat": " write: {{project_name}}", "range": true, "refId": "C" } @@ -732,7 +741,7 @@ }, "id": 15, "panels": [], - "title": "Per Hypervisor Free Capacity", + "title": "Per Instance Utilization", "type": "row" }, { @@ -740,67 +749,319 @@ "type": "prometheus", "uid": "${datasource}" }, - "description": "", "fieldConfig": { "defaults": { "color": { - "mode": "thresholds" + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMax": 1, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 23, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "semi-dark-yellow", + "color": "green", "value": null }, { - "color": "green", - "value": 4 + "color": "red", + "value": 80 } ] - } + }, + "unit": "percentunit" }, "overrides": [] }, "gridPos": { - "h": 10, - "w": 24, + "h": 9, + "w": 8, "x": 0, "y": 30 }, - "id": 2, + "id": 23, + "interval": "30m", "options": { - "displayMode": "basic", - "minVizHeight": 10, - "minVizWidth": 0, - "orientation": "horizontal", - "reduceOptions": { + "legend": { "calcs": [ - "lastNotNull" + "min", + "max" ], - "fields": "", - "values": false + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Max", + "sortDesc": true }, - "showUnfilled": true + "tooltip": { + "mode": "single", + "sort": "none" + } }, - "pluginVersion": "9.4.7", "targets": [ { "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "editorMode": "builder", - "expr": "openstack_free_capacity_by_flavor_total", - "format": "time_series", - "legendFormat": "{{flavor_name}}", + "editorMode": "code", + "expr": "sum(irate(libvirt_domain_vcpu_time_seconds_total{}[5m]) / ignoring(instance,vcpu) group_left(domain) libvirt_domain_info_virtual_cpus{}) by (domain) * on(domain) group_left(instance_name,project_name,project_uuid) libvirt_domain_info_meta{project_uuid=~\"${project_id}\"}", + "hide": false, + "legendFormat": "{{instance_name}}", + "range": true, + "refId": "B" + } + ], + "title": "CPU utilization per instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 23, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 100, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 8, + "y": 30 + }, + "id": 24, + "interval": "30m", + "options": { + "legend": { + "calcs": [ + "min", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Max", + "sortDesc": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "libvirt_domain_memory_stats_used_percent * on(domain) group_left(instance_name,project_name,project_uuid) libvirt_domain_info_meta{project_uuid=~\"${project_id}\"}", + "hide": false, + "legendFormat": "{{instance_name}}", "range": true, "refId": "A" } ], - "title": "Free Capacity by Flavor", - "type": "bargauge" + "title": "Memory utilization per instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": true, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 23, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 1, + "min": -1, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 16, + "y": 30 + }, + "id": 25, + "options": { + "legend": { + "calcs": [ + "min", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Max", + "sortDesc": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "rate(libvirt_domain_block_stats_read_time_seconds_total[5m]) * on(domain) group_left(instance_name,project_name,project_uuid) libvirt_domain_info_meta{project_uuid=~\"${project_id}\"}", + "hide": false, + "legendFormat": "{{instance_name}} : read {{target_device}}", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "avg(rate(libvirt_domain_block_stats_write_time_seconds_total[5m]) * on(domain) group_left(instance_name,project_name,project_uuid) libvirt_domain_info_meta{project_uuid=~\"${project_id}\"} * -1) by (project_name)", + "hide": false, + "legendFormat": "{{instance_name}} : write {{target_device}}", + "range": true, + "refId": "C" + } + ], + "title": "Disk utilization per instance", + "type": "timeseries" } ], "refresh": "", @@ -817,7 +1078,7 @@ "current": { "selected": false, "text": "Prometheus", - "value": "Prometheus" + "value": "PBFA97CFB590B2093" }, "description": "The prometheus datasource used for queries.", "hide": 0, @@ -867,14 +1128,14 @@ ] }, "time": { - "from": "now-3h", + "from": "now-2d", "to": "now" }, "timepicker": {}, "timezone": "", "title": "OpenStack Project Metrics", "uid": "mXiuBDe7z", - "version": 2, + "version": 1, "weekStart": "" } -{% endraw %} +{% endraw %} \ No newline at end of file diff --git a/etc/kayobe/kolla/config/haproxy/services.d/os_exporter.cfg b/etc/kayobe/kolla/config/haproxy/services.d/os_capacity.cfg similarity index 74% rename from etc/kayobe/kolla/config/haproxy/services.d/os_exporter.cfg rename to etc/kayobe/kolla/config/haproxy/services.d/os_capacity.cfg index e40c27a38..4326265ca 100644 --- a/etc/kayobe/kolla/config/haproxy/services.d/os_exporter.cfg +++ b/etc/kayobe/kolla/config/haproxy/services.d/os_capacity.cfg @@ -6,7 +6,11 @@ frontend os_capacity_frontend option httplog option forwardfor http-request set-header X-Forwarded-Proto https if { ssl_fc } - bind {{ kolla_internal_vip_address }}:9000 +{% if kolla_enable_tls_internal | bool %} + bind {{ kolla_internal_vip_address }}:9090 ssl crt /etc/haproxy/haproxy-internal.pem +{% else %} + bind {{ kolla_internal_vip_address }}:9090 +{% endif %} default_backend os_capacity_backend backend os_capacity_backend diff --git a/etc/kayobe/kolla/config/prometheus/prometheus.yml.d/70-oscapacity.yml b/etc/kayobe/kolla/config/prometheus/prometheus.yml.d/70-oscapacity.yml index 659c26047..afed8d915 100644 --- a/etc/kayobe/kolla/config/prometheus/prometheus.yml.d/70-oscapacity.yml +++ b/etc/kayobe/kolla/config/prometheus/prometheus.yml.d/70-oscapacity.yml @@ -6,8 +6,11 @@ scrape_configs: - job_name: os-capacity static_configs: - targets: - - '{{ kolla_internal_vip_address | put_address_in_context('url') }}:9000' + - '{{ kolla_internal_fqdn | put_address_in_context('url') }}:9090' scrape_interval: 15m scrape_timeout: 10m +{% if kolla_enable_tls_internal | bool %} + scheme: https +{% endif %} {% endraw %} {% endif %} diff --git a/etc/kayobe/stackhpc-monitoring.yml b/etc/kayobe/stackhpc-monitoring.yml index b48646e79..8d0771e13 100644 --- a/etc/kayobe/stackhpc-monitoring.yml +++ b/etc/kayobe/stackhpc-monitoring.yml @@ -15,3 +15,13 @@ alertmanager_low_memory_threshold_gib: 5 # Enabling this flag will result in HAProxy configuration and Prometheus scrape # targets being templated during deployment. stackhpc_enable_os_capacity: false + +# Keystone authentication URL for OpenStack Capacity +stackhpc_os_capacity_auth_url: "http{% if kolla_enable_tls_internal | bool %}s{% endif %}://{{ kolla_internal_fqdn }}:5000" + +# OpenStack region for OpenStack Capacity +stackhpc_os_capacity_openstack_region_name: "{{ openstack_region_name | default(RegionOne) }}" + +# Whether TLS certificate verification is enabled for the OpenStack Capacity +# exporter during Keystone authentication. +stackhpc_os_capacity_openstack_verify: true diff --git a/releasenotes/notes/os-capacity-94006f03f16583e4.yaml b/releasenotes/notes/os-capacity-94006f03f16583e4.yaml index f9d76b7f4..ca317682b 100644 --- a/releasenotes/notes/os-capacity-94006f03f16583e4.yaml +++ b/releasenotes/notes/os-capacity-94006f03f16583e4.yaml @@ -9,7 +9,20 @@ upgrade: - | To deploy the OpenStack Capacity Grafana dashboard, you must define OpenStack application credential variables: - ``secrets_os_exporter_auth_url``, - ``secrets_os_exporter_credential_id`` and - ``secrets_os_exporter_credential_secret`` as laid out in the + ``secrets_os_capacity_credential_id`` and + ``secrets_os_capacity_credential_secret`` as laid out in the 'Monitoring' documentation. + + You must also enable the ``stackhpc_enable_os_capacity`` + flag for OpenStack Capacity HAProxy and Prometheus configuration + to be templated. + + You may also change the default authentication URL from the + kolla_internal_fqdn and change the default OpenStack region + from RegionOne with the variables: + ``stackhpc_os_capacity_auth_url`` and + ``stackhpc_os_capacity_openstack_region_name``. + + To disable certificate verification for the OpenStack Capacity + exporter, you can set ``stackhpc_os_capacity_openstack_verify`` + to false. From 75f48577750030ba6720bcf2060734a67331d181 Mon Sep 17 00:00:00 2001 From: Seunghun Lee <45145778+seunghun1ee@users.noreply.github.com> Date: Wed, 21 Feb 2024 10:24:29 +0000 Subject: [PATCH 031/128] Update Kolla container images for Ubuntu Jammy Zed (#904) * Update ubuntu jammy kolla container tags --- etc/kayobe/kolla-image-tags.yml | 12 ++---------- ...-jammy-zed-kolla-containers-0774af3c590b89d0.yaml | 4 ++++ 2 files changed, 6 insertions(+), 10 deletions(-) create mode 100644 releasenotes/notes/update-ubuntu-jammy-zed-kolla-containers-0774af3c590b89d0.yaml diff --git a/etc/kayobe/kolla-image-tags.yml b/etc/kayobe/kolla-image-tags.yml index 0144c8179..5cd94a2c3 100644 --- a/etc/kayobe/kolla-image-tags.yml +++ b/etc/kayobe/kolla-image-tags.yml @@ -5,15 +5,7 @@ kolla_image_tags: openstack: rocky-9: zed-rocky-9-20240202T105829 - ubuntu-jammy: zed-ubuntu-jammy-20230921T153510 - bifrost: - ubuntu-jammy: zed-ubuntu-jammy-20231101T132522 - ovn: - ubuntu-jammy: zed-ubuntu-jammy-20230821T155947 - cloudkitty: - ubuntu-jammy: zed-ubuntu-jammy-20231114T124701 + ubuntu-jammy: zed-ubuntu-jammy-20240129T151534 neutron: rocky-9: zed-rocky-9-20240202T141530 - ubuntu-jammy: zed-ubuntu-jammy-20231115T094053 - opensearch: - ubuntu-jammy: zed-ubuntu-jammy-20231214T095452 + ubuntu-jammy: zed-ubuntu-jammy-20240202T143208 diff --git a/releasenotes/notes/update-ubuntu-jammy-zed-kolla-containers-0774af3c590b89d0.yaml b/releasenotes/notes/update-ubuntu-jammy-zed-kolla-containers-0774af3c590b89d0.yaml new file mode 100644 index 000000000..81562019d --- /dev/null +++ b/releasenotes/notes/update-ubuntu-jammy-zed-kolla-containers-0774af3c590b89d0.yaml @@ -0,0 +1,4 @@ +--- +upgrade: + - | + Update Ubuntu Jammy Zed Kolla container tags. From 0e7ea643f1f4a82fb3382b81cc3172bb7e85f802 Mon Sep 17 00:00:00 2001 From: technowhizz <7688823+technowhizz@users.noreply.github.com> Date: Thu, 28 Dec 2023 09:48:15 +0000 Subject: [PATCH 032/128] Ensure cron service is started for smartmon --- etc/kayobe/ansible/smartmon-tools.yml | 7 +++++++ releasenotes/notes/smartmontools-bc8176f45d58a75d.yaml | 6 ++++++ 2 files changed, 13 insertions(+) create mode 100644 releasenotes/notes/smartmontools-bc8176f45d58a75d.yaml diff --git a/etc/kayobe/ansible/smartmon-tools.yml b/etc/kayobe/ansible/smartmon-tools.yml index bb5cf5dca..b4a064b63 100644 --- a/etc/kayobe/ansible/smartmon-tools.yml +++ b/etc/kayobe/ansible/smartmon-tools.yml @@ -12,6 +12,13 @@ state: present become: true + - name: Ensure the cron/crond service is running + service: + name: "{{ 'cron' if ansible_facts['distribution'] == 'Ubuntu' else 'crond' }}" + state: started + enabled: true + become: true + - name: Copy smartmon.sh and nvmemon.sh from scripts folder copy: src: "scripts/{{ item }}" diff --git a/releasenotes/notes/smartmontools-bc8176f45d58a75d.yaml b/releasenotes/notes/smartmontools-bc8176f45d58a75d.yaml new file mode 100644 index 000000000..ac3451347 --- /dev/null +++ b/releasenotes/notes/smartmontools-bc8176f45d58a75d.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + The smartmon-tools playbook now ensures that the cron service is running as + in some cases it may not be running by default. + From 3fca4743f02f877ded36d9bcee763019733e1f73 Mon Sep 17 00:00:00 2001 From: Matt Crees Date: Wed, 21 Feb 2024 11:22:21 +0000 Subject: [PATCH 033/128] Bump RL9 host image to RL9.3 (#897) Co-authored-by: Mark Goddard --- etc/kayobe/pulp-host-image-versions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/kayobe/pulp-host-image-versions.yml b/etc/kayobe/pulp-host-image-versions.yml index 9c55926e5..40819e32b 100644 --- a/etc/kayobe/pulp-host-image-versions.yml +++ b/etc/kayobe/pulp-host-image-versions.yml @@ -1,5 +1,5 @@ --- # Overcloud host image versioning tags # These images must be in SMS, since they are used by our AIO CI runners -stackhpc_rocky_9_overcloud_host_image_version: "zed-20231106T151621" +stackhpc_rocky_9_overcloud_host_image_version: "zed-20240126T093155" stackhpc_ubuntu_jammy_overcloud_host_image_version: "zed-20231013T123933" From ffe3ed3183d046c8ec98690765190bda1cb4c243 Mon Sep 17 00:00:00 2001 From: Alex-Welsh Date: Thu, 15 Feb 2024 17:27:39 +0000 Subject: [PATCH 034/128] Add Ubuntu Jammy upgrade doc --- doc/source/operations/index.rst | 1 + doc/source/operations/ubuntu-jammy.rst | 531 +++++++++++++++++++++++++ 2 files changed, 532 insertions(+) create mode 100644 doc/source/operations/ubuntu-jammy.rst diff --git a/doc/source/operations/index.rst b/doc/source/operations/index.rst index 284795d6a..f547f13bb 100644 --- a/doc/source/operations/index.rst +++ b/doc/source/operations/index.rst @@ -11,4 +11,5 @@ This guide is for operators of the StackHPC Kayobe configuration project. octavia hotfix-playbook rocky-linux-9 + ubuntu-jammy secret-rotation diff --git a/doc/source/operations/ubuntu-jammy.rst b/doc/source/operations/ubuntu-jammy.rst new file mode 100644 index 000000000..4f684aa07 --- /dev/null +++ b/doc/source/operations/ubuntu-jammy.rst @@ -0,0 +1,531 @@ +========================= +Upgrading to Ubuntu Jammy +========================= + +Overview +======== + +This document describes how to upgrade systems from Ubuntu Focal 20.04 to +Ubuntu Jammy 22.04. This procedure must be performed on Ubuntu Focal 20.04 +OpenStack Yoga systems before it is possible to upgrade to OpenStack Zed. It is +possible to perform a rolling upgrade to ensure service is not disrupted. + +Upgrades are performed in-place with a script using the ``do-release-upgrade`` +tool provided by Canonical, rather than reprovisioning. The scripts are found +at ``tools/ubuntu-upgrade-*.sh``. For overcloud and infrastructure VM upgrades, +the script takes one argument - the host(s) to upgrade. The scripts execute a +playbook to upgrade the host, then run the appropriate ``kayobe * host +configure`` command. + +The guide assumes a local pulp instance is deployed and all hosts use it +to pull ``apt`` packages. To upgrade a host using upstream packages, see the +manual upgrade process at the bottom of this page. + +While it is technically possible to upgrade hosts in any order, it is +recommended that upgrades for one type of node be completed before moving on +to the next i.e. all compute node upgrades are performed before all storage +node upgrades. + +The order of node groups is less important however it is arguably safest to +perform controller node upgrades first, given that they are the most complex +and it is easiest to revert their state in the event of a failure. +This guide covers the following types of hosts: + +- Controllers +- Compute hosts +- Storage hosts +- Seed +- Other hosts not managed by Kayobe + +The following types of hosts will be covered in the future: + +- Ansible control host +- Seed hypervisor (an upgrade script exists but has not been tested) +- Infrastructure VMs (an upgrade script exists but has not been tested) + +.. warning:: + + Ceph node upgrades have not yet been performed outside of a virtualised test + environment. Proceed with caution. + +Prerequisites +============= + +Before starting the upgrade, ensure any appropriate prerequisites are +satisfied. These will be specific to each deployment, but here are some +suggestions: + +* Merge in the latest ``stackhpc-kayobe-config`` ``stackhpc/yoga`` branch. +* Ensure that there is sufficient hypervisor capacity to drain + at least one node. +* If using Ironic for bare metal compute, ensure that at least one node is + available for testing provisioning. +* Ensure that expected test suites are passing, e.g. Tempest. +* Resolve any Prometheus alerts. +* Check for unexpected ``ERROR`` or ``CRITICAL`` messages in Kibana/OpenSearch + Dashboard. +* Check Grafana dashboards. + +Sync Release Train artifacts +---------------------------- + +New `StackHPC Release Train <../configuration/release-train.html>`__ content +should be synced to the local Pulp server. This includes host packages +(Deb/RPM) and container images. + +To sync host packages: + +.. code-block:: console + + kayobe playbook run $KAYOBE_CONFIG_PATH/ansible/pulp-repo-sync.yml -e stackhpc_pulp_sync_ubuntu_focal=true -e stackhpc_pulp_sync_ubuntu_jammy=true + kayobe playbook run $KAYOBE_CONFIG_PATH/ansible/pulp-repo-publish.yml + +Once the host package content has been tested in a test/staging environment, it +may be promoted to production: + +.. code-block:: console + + kayobe playbook run $KAYOBE_CONFIG_PATH/ansible/pulp-repo-promote-production.yml + +To sync container images: + +.. code-block:: console + + kayobe playbook run $KAYOBE_CONFIG_PATH/ansible/pulp-container-sync.yml + kayobe playbook run $KAYOBE_CONFIG_PATH/ansible/pulp-container-publish.yml + +Build locally customised container images +----------------------------------------- + +.. note:: + + The container images provided by StackHPC Release Train are suitable for + most deployments. In this case, this step can be skipped. + +In some cases, it is necessary to build some or all images locally to apply +customisations. To do this, set +``stackhpc_pulp_sync_for_local_container_build`` to ``true`` before syncing +container images. + +To build the overcloud images locally and push them to the local Pulp server: + +.. code-block:: console + + kayobe overcloud container image build --push + +It is possible to build a specific set of images by supplying one or more +image name regular expressions: + +.. code-block:: console + + kayobe overcloud container image build --push ironic- nova-api + +Deploy the latest container images +---------------------------------- + +Make sure you deploy the latest containers before this upgrade: + +.. code-block:: console + + kayobe seed service deploy + kayobe overcloud service deploy + +Common issues for all host types +================================ + +- Interface names regularly change during upgrades, usually gaining the + ``np0`` suffix. This cannot easily be resolved. The upgrade script + configures networking both before and after rebooting to apply the upgrade. + Setting the interface statically in a kayobe-config fails during one of + these. This can be worked around by adding a ``sed`` command to the upgrade + script between the upgrade playbook step and the host configure step e.g. + + .. code-block:: bash + + kayobe playbook run $KAYOBE_CONFIG_PATH/ansible/ubuntu-upgrade.yml -e os_release=jammy --limit $1 + sed -i -e 's/"ens1"/"ens1np0"/g' -e 's/"ens2"/"ens2np0"/g' $KAYOBE_CONFIG_PATH/environments/production/inventory/group_vars/compute/network-interfaces + kayobe overcloud host configure --limit $1 --kolla-limit $1 -e os_release=jammy + + Remember to reset the change before upgrading another host (or add a + second ``sed`` command to automate the process) +- Disk names can change during upgrades. This can be resolved in kayobe-config + once the new name is known (i.e. after the first upgrade) and applied by + re-running ``host configure`` for the affected host. +- Timeouts can become an issue with some hardware. The host will reboot once + or twice depending on whether it needs to apply package updates. Edit the + timeouts in the upgrade playbook (``ubuntu-upgrade.yml``) where required. + +Controllers +=========== + +Upgrade controllers *one by one*, ideally upgrading the host with the Kolla +Virtual IP (VIP) last. Before upgrading a host with the VIP, stop the +``keepalived`` container for a few seconds to fail it over to another +controller (restarting the container does not always stop the container for +long enough). + +.. code-block:: bash + + sudo docker stop keepalived + sudo docker start keepalived + +Always back up the overcloud DB before starting: + +.. code-block:: bash + + kayobe overcloud database backup + +Potential issues +---------------- + +- In both testing and production, RabbitMQ has fallen into an error state + during controller upgrades. Keep an eye on the RabbitMQ Grafana dashboard and + if errors begin to increase, use the ``rabbitmq-reset`` playbook: + + .. code-block:: bash + + kayobe playbook run $KAYOBE_CONFIG_PATH/ansible/rabbitmq-reset.yml + +- If you are using hyper-converged Ceph, please also note the potential issues + in the Storage section below. + +Full procedure for one controller +--------------------------------- + +1. Export the ``KAYOBE_PATH`` environment variable e.g. + + .. code-block:: console + + export KAYOBE_PATH=~/src/kayobe + +2. If the controller is running Ceph services: + + 1. Set host in maintenance mode: + + .. code-block:: console + + ceph orch host maintenance enter + + 2. Check nothing remains on the host: + + .. code-block:: console + + ceph orch ps + +3. Run the upgrade script: + + .. code-block:: console + + $KAYOBE_CONFIG_PATH/../../tools/ubuntu-upgrade-overcloud.sh + +4. If the controller is running Ceph OSD services: + + 1. Make sure the cephadm public key is in ``authorized_keys`` for stack or + root user - depends on your setup. For example, your SSH key may + already be defined in ``users.yml``. If in doubt, run the cephadm + deploy playbook to copy the SSH key and install the cephadm binary. + + .. code-block:: console + + kayobe playbook run $KAYOBE_CONFIG_PATH/ansible/cephadm-deploy.yml + + 2. Take the host out of maintenance mode: + + .. code-block:: console + + ceph orch host maintenance exit + + 3. Make sure that everything is back in working condition before moving + on to the next host: + + .. code-block:: console + + ceph -s + ceph -w + +5. Some RabbitMQ instability has been observed. Check the RabbitMQ dashboard + in Grafana if the cluster is unhealthy run the ``rabbitmq-reset`` playbook. + + .. code:: console + + kayobe playbook run $KAYOBE_CONFIG_PATH/ansible/rabbitmq-reset.yml + +After each controller has been upgraded you may wish to perform some smoke +testing, run Tempest, check for alerts and errors etc. + +Compute +======= + +Compute nodes can be upgraded in batches. +The possible batches depend on: + +* willingness for instance reboots and downtime +* available spare hypervisor capacity +* sizes of groups of compatible hypervisors + +Potential issues +---------------- + +- VMs cannot be live migrated between Focal and Jammy hypervisors using AMD + CPUs. Any affected VMs must be cold-migrated. It may be possible to disable + ``xsave``, reboot the VM, then live-migrate, however this process has not + been tested. + +Full procedure for one batch of hosts +------------------------------------- + +1. Export the ``KAYOBE_PATH`` environment variable e.g. + + .. code-block:: console + + export KAYOBE_PATH=~/src/kayobe + +2. Disable the Nova compute service and drain it of VMs using live migration. + If any VMs fail to migrate, they may be cold migrated or powered off: + + .. code-block:: console + + kayobe playbook run $KAYOBE_CONFIG_PATH/ansible/nova-compute-{disable,drain}.yml --limit + +3. If the compute node is running Ceph OSD services: + + 1. Set host in maintenance mode: + + .. code-block:: console + + ceph orch host maintenance enter + + 2. Check there's nothing remaining on the host: + + .. code-block:: console + + ceph orch ps + +4. Run the upgrade script: + + .. code-block:: console + + $KAYOBE_CONFIG_PATH/../../tools/ubuntu-upgrade-overcloud.sh + +5. If the compute node is running Ceph OSD services: + + 1. Make sure the cephadm public key is in ``authorized_keys`` for stack or + root user - depends on your setup. For example, your SSH key may + already be defined in ``users.yml`` . If in doubt, run the cephadm + deploy playbook to copy the SSH key and install the cephadm binary. + + .. code-block:: console + + kayobe playbook run $KAYOBE_CONFIG_PATH/ansible/cephadm-deploy.yml + + 2. Take the host out of maintenance mode: + + .. code-block:: console + + ceph orch host maintenance exit + + 3. Make sure that everything is back in working condition before moving + on to the next host: + + .. code-block:: console + + ceph -s + ceph -w + +6. Restore the system to full health. + + 1. If any VMs were powered off, they may now be powered back on. + + 2. Wait for Prometheus alerts and errors in Kibana/OpenSearch Dashboard to + resolve, or address them. + + 3. Once happy that the system has been restored to full health, enable the + hypervisor in Nova if it is still disabled and then move onto the next + host or batch or hosts. + + .. code-block:: console + + kayobe playbook run $KAYOBE_CONFIG_PATH/ansible/nova-compute-enable.yml --limit + +Storage +======= + +Potential issues +---------------- + +- It is recommended that you upgrade the bootstrap host last. +- Before upgrading the bootstrap host, it can be beneficial to backup + ``/etc/ceph`` and ``/var/lib/ceph``, as sometimes the keys, config, etc. + stored here will not be moved/recreated correctly. +- When a host is taken out of maintenance, you may see errors relating to + permissions of /tmp/etc and /tmp/var. These issues should be resolved in + Ceph version 17.2.7. See issue: https://github.com/ceph/ceph/pull/50736. In + the meantime, you can work around this by running the command below. You may + need to omit one or the other of ``/tmp/etc`` and ``/tmp/var``. You will + likely need to run this multiple times. Run ``ceph -W cephadm`` to monitor + the logs and see when permissions issues are hit. + + .. code-block:: console + + kayobe overcloud host command run --command "chown -R stack:stack /tmp/etc /tmp/var" -b -l storage + +- It has been seen that sometimes the Ceph containers do not come up after + upgrading. This seems to be related to having ``/var/lib/ceph`` persisted + through the reprovision (e.g. seen at a customer in a volume with software + RAID). Further investigation is needed for the root cause. When this + occurs, you will need to redeploy the daemons: + + List the daemons on the host: + + .. code-block:: console + + ceph orch ps + + Redeploy the daemons, one at a time. It is recommended that you start with + the crash daemon, as this will have the least impact if unexpected issues + occur. + + .. code-block:: console + + ceph orch daemon redeploy to redeploy a daemon. + +- Commands starting with ``ceph`` are all run on the cephadm bootstrap + host in a cephadm shell unless stated otherwise. + +Full procedure for a storage host +--------------------------------- + +1. Export the ``KAYOBE_PATH`` environment variable e.g. + + .. code-block:: console + + export KAYOBE_PATH=~/src/kayobe + +2. Set host in maintenance mode: + + .. code-block:: console + + ceph orch host maintenance enter + +3. Check there's nothing remaining on the host: + + .. code-block:: console + + ceph orch ps + +4. Run the upgrade script: + + .. code-block:: console + + $KAYOBE_CONFIG_PATH/../../tools/ubuntu-upgrade-overcloud.sh + +5. Make sure the cephadm public key is in ``authorized_keys`` for stack or + root user - depends on your setup. For example, your SSH key may + already be defined in ``users.yml``. If in doubt, run the cephadm + deploy playbook to copy the SSH key and install the cephadm binary. + + .. code-block:: console + + kayobe playbook run $KAYOBE_CONFIG_PATH/ansible/cephadm-deploy.yml + +6. Take the host out of maintenance mode: + + .. code-block:: console + + ceph orch host maintenance exit + +7. Make sure that everything is back in working condition before moving + on to the next host: + + .. code-block:: console + + ceph -s + ceph -w + +Seed +==== + +Potential issues +---------------- + +- The process has not been tested as well as for other hosts. Proceed with + caution. +- The Seed can take significantly longer to upgrade than other hosts. + ``do-release-upgrade`` has been observed taking more than 45 minutes to + complete. + +Full procedure +-------------- + +1. Export the ``KAYOBE_PATH`` environment variable e.g. + + .. code-block:: console + + export KAYOBE_PATH=~/src/kayobe + +2. Run the upgrade script: + + .. code-block:: console + + $KAYOBE_CONFIG_PATH/../../tools/ubuntu-upgrade-seed.sh + +Wazuh manager +============= + +TODO + +Seed hypervisor +=============== + +TODO + +Ansible control host +==================== + +TODO + +Manual Process +============== + +Sometimes it is necessary to upgrade a system that is not managed by Kayobe +(and therefore does not use packages from pulp). Below is a set of instructions +to manually execute the upgrade process. + +Full procedure +-------------- + +1. Update all packages to the latest available versions + + .. code-block:: console + + sudo apt update -y && sudo apt upgrade -y + +2. Install the upgrade tool + + .. code-block:: console + + sudo apt install ubuntu-release-upgrader-core + +3. Check whether a reboot is required + + .. code-block:: console + + cat /var/run/reboot-required + +4. Where required, reboot to apply updates + + .. code-block:: console + + sudo reboot + +5. Run ``do-release-upgrade`` + + .. code-block:: console + + do-release-upgrade -f DistUpgradeViewNonInteractive + +6. Reboot to apply the upgrade + + .. code-block:: console + + sudo reboot From e22e6e1b98a3b3237da1cf7bb51885447577d807 Mon Sep 17 00:00:00 2001 From: Seunghun Lee <45145778+seunghun1ee@users.noreply.github.com> Date: Wed, 21 Feb 2024 13:16:49 +0000 Subject: [PATCH 035/128] Update neutron tag to include OVS images --- etc/kayobe/kolla-image-tags.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/etc/kayobe/kolla-image-tags.yml b/etc/kayobe/kolla-image-tags.yml index 3f095e7e1..f962babb4 100644 --- a/etc/kayobe/kolla-image-tags.yml +++ b/etc/kayobe/kolla-image-tags.yml @@ -12,3 +12,4 @@ kolla_image_tags: rocky-9: 2023.1-rocky-9-20240205T162323 neutron: rocky-9: 2023.1-rocky-9-20240202T145927 + ubuntu-jammy: 2023.1-ubuntu-jammy-20240221T103817 From b7d5113b26c577eda10241f47a29068be815f332 Mon Sep 17 00:00:00 2001 From: Seunghun Lee Date: Wed, 21 Feb 2024 13:52:27 +0000 Subject: [PATCH 036/128] Add missing haproxy and letsencrypt images --- etc/kayobe/kolla-image-tags.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/etc/kayobe/kolla-image-tags.yml b/etc/kayobe/kolla-image-tags.yml index f962babb4..ab46cb4cc 100644 --- a/etc/kayobe/kolla-image-tags.yml +++ b/etc/kayobe/kolla-image-tags.yml @@ -8,8 +8,10 @@ kolla_image_tags: ubuntu-jammy: 2023.1-ubuntu-jammy-20240129T151608 haproxy_ssh: rocky-9: 2023.1-rocky-9-20240205T162323 + ubuntu-jammy: 2023.1-ubuntu-jammy-20240221T133905 letsencrypt: rocky-9: 2023.1-rocky-9-20240205T162323 + ubuntu-jammy: 2023.1-ubuntu-jammy-20240221T133905 neutron: rocky-9: 2023.1-rocky-9-20240202T145927 ubuntu-jammy: 2023.1-ubuntu-jammy-20240221T103817 From 27ebaa5fd65d6e940b9fa3aa9cd3ebb4543aef6b Mon Sep 17 00:00:00 2001 From: Seunghun Lee Date: Thu, 15 Feb 2024 10:20:22 +0000 Subject: [PATCH 037/128] Set kolla_build_neutron_ovs to true if regex empty --- .github/workflows/stackhpc-container-image-build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/stackhpc-container-image-build.yml b/.github/workflows/stackhpc-container-image-build.yml index b8afea93e..af9b3cc79 100644 --- a/.github/workflows/stackhpc-container-image-build.yml +++ b/.github/workflows/stackhpc-container-image-build.yml @@ -171,6 +171,9 @@ jobs: if ${{ inputs.push }} == 'true'; then args="$args --push" fi + if [[ ${{ github.event.inputs.regexes }} == "" ]]; then + args="$args -e kolla_build_neutron_ovs=true" + fi source venvs/kayobe/bin/activate && source src/kayobe-config/kayobe-env --environment ci-builder && kayobe overcloud container image build $args From a9a39806b1fa10d2321d34452fbe00bbe50b0136 Mon Sep 17 00:00:00 2001 From: Seunghun Lee Date: Wed, 21 Feb 2024 13:30:00 +0000 Subject: [PATCH 038/128] Add missing quotes --- .github/workflows/stackhpc-container-image-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stackhpc-container-image-build.yml b/.github/workflows/stackhpc-container-image-build.yml index af9b3cc79..be09f8b4f 100644 --- a/.github/workflows/stackhpc-container-image-build.yml +++ b/.github/workflows/stackhpc-container-image-build.yml @@ -171,7 +171,7 @@ jobs: if ${{ inputs.push }} == 'true'; then args="$args --push" fi - if [[ ${{ github.event.inputs.regexes }} == "" ]]; then + if [[ "${{ github.event.inputs.regexes }}" == "" ]]; then args="$args -e kolla_build_neutron_ovs=true" fi source venvs/kayobe/bin/activate && From 2249baf6d3c76bfa9940b927fc7e7571c5902fdd Mon Sep 17 00:00:00 2001 From: Seunghun Lee Date: Wed, 21 Feb 2024 15:14:43 +0000 Subject: [PATCH 039/128] Change the variable definition location --- .github/workflows/stackhpc-container-image-build.yml | 3 --- etc/kayobe/environments/ci-builder/stackhpc-ci.yml | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/stackhpc-container-image-build.yml b/.github/workflows/stackhpc-container-image-build.yml index be09f8b4f..b8afea93e 100644 --- a/.github/workflows/stackhpc-container-image-build.yml +++ b/.github/workflows/stackhpc-container-image-build.yml @@ -171,9 +171,6 @@ jobs: if ${{ inputs.push }} == 'true'; then args="$args --push" fi - if [[ "${{ github.event.inputs.regexes }}" == "" ]]; then - args="$args -e kolla_build_neutron_ovs=true" - fi source venvs/kayobe/bin/activate && source src/kayobe-config/kayobe-env --environment ci-builder && kayobe overcloud container image build $args diff --git a/etc/kayobe/environments/ci-builder/stackhpc-ci.yml b/etc/kayobe/environments/ci-builder/stackhpc-ci.yml index 868668e33..27ac8d858 100644 --- a/etc/kayobe/environments/ci-builder/stackhpc-ci.yml +++ b/etc/kayobe/environments/ci-builder/stackhpc-ci.yml @@ -29,6 +29,7 @@ kolla_enable_ovn: true kolla_enable_prometheus: true kolla_enable_redis: true kolla_enable_skydive: true +kolla_build_neutron_ovs: true ############################################################################### # StackHPC configuration. From d5dff92232c8567d9b25053a3dc9c6ed3cdde7f6 Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Thu, 22 Feb 2024 13:07:45 +0100 Subject: [PATCH 040/128] Fix query for top Ceph pools by capacity used This panel was only showing the most used pool instead of as many pools as configured with the ``$topk`` variable. Signed-off-by: Pierre Riteau --- .../kolla/config/grafana/dashboards/ceph/ceph_pools.json | 2 +- ...ceph-pools-top-capacity-used-panel-26d495c45f2678c8.yaml | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/fix-ceph-pools-top-capacity-used-panel-26d495c45f2678c8.yaml diff --git a/etc/kayobe/kolla/config/grafana/dashboards/ceph/ceph_pools.json b/etc/kayobe/kolla/config/grafana/dashboards/ceph/ceph_pools.json index f2882ed60..b3a4af1bd 100644 --- a/etc/kayobe/kolla/config/grafana/dashboards/ceph/ceph_pools.json +++ b/etc/kayobe/kolla/config/grafana/dashboards/ceph/ceph_pools.json @@ -657,7 +657,7 @@ ], "targets": [ { - "expr": "topk(1,((ceph_pool_stored / (ceph_pool_stored + ceph_pool_max_avail)) * on(pool_id) group_left(name) ceph_pool_metadata))", + "expr": "topk($topk,((ceph_pool_stored / (ceph_pool_stored + ceph_pool_max_avail)) * on(pool_id) group_left(name) ceph_pool_metadata))", "format": "table", "hide": false, "instant": true, diff --git a/releasenotes/notes/fix-ceph-pools-top-capacity-used-panel-26d495c45f2678c8.yaml b/releasenotes/notes/fix-ceph-pools-top-capacity-used-panel-26d495c45f2678c8.yaml new file mode 100644 index 000000000..cdf38bb3a --- /dev/null +++ b/releasenotes/notes/fix-ceph-pools-top-capacity-used-panel-26d495c45f2678c8.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixes Grafana panel of top Ceph pools by capacity used. This panel was only + showing the most used pool instead of as many pools as configured with the + ``$topk`` variable. From 93ba47e7496ce78a5d8e09483375ca3f840a39fd Mon Sep 17 00:00:00 2001 From: Jake Hutchinson Date: Thu, 22 Feb 2024 15:55:26 +0000 Subject: [PATCH 041/128] Default to RegionOne for os-capacity --- etc/kayobe/stackhpc-monitoring.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/kayobe/stackhpc-monitoring.yml b/etc/kayobe/stackhpc-monitoring.yml index 8d0771e13..13bf6ba0f 100644 --- a/etc/kayobe/stackhpc-monitoring.yml +++ b/etc/kayobe/stackhpc-monitoring.yml @@ -20,7 +20,7 @@ stackhpc_enable_os_capacity: false stackhpc_os_capacity_auth_url: "http{% if kolla_enable_tls_internal | bool %}s{% endif %}://{{ kolla_internal_fqdn }}:5000" # OpenStack region for OpenStack Capacity -stackhpc_os_capacity_openstack_region_name: "{{ openstack_region_name | default(RegionOne) }}" +stackhpc_os_capacity_openstack_region_name: "RegionOne" # Whether TLS certificate verification is enabled for the OpenStack Capacity # exporter during Keystone authentication. From 0aa2c97ab5b7b6c298db1b36bb4f684b8a140fa6 Mon Sep 17 00:00:00 2001 From: Grzegorz Koper Date: Wed, 24 Jan 2024 10:32:25 +0100 Subject: [PATCH 042/128] Moving variable from play to group_vars This lets us override in case one of the machines have different VG name Otherwise play variables take priority. --- etc/kayobe/ansible/growroot.yml | 2 -- etc/kayobe/inventory/group_vars/all/growroot | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 etc/kayobe/inventory/group_vars/all/growroot diff --git a/etc/kayobe/ansible/growroot.yml b/etc/kayobe/ansible/growroot.yml index aa3dcc05a..333991aa0 100644 --- a/etc/kayobe/ansible/growroot.yml +++ b/etc/kayobe/ansible/growroot.yml @@ -23,8 +23,6 @@ ansible_python_interpreter: /usr/bin/python3 # Work around no known_hosts entry on first boot. ansible_ssh_common_args: "-o StrictHostKeyChecking=no" - # Name of the LVM VG containing the root PV. - growroot_vg: "rootvg" # Don't assume facts are present. os_family: "{{ ansible_facts.os_family | default('Debian' if os_distribution == 'ubuntu' else 'RedHat') }}" # Ignore LVM check diff --git a/etc/kayobe/inventory/group_vars/all/growroot b/etc/kayobe/inventory/group_vars/all/growroot new file mode 100644 index 000000000..280cee665 --- /dev/null +++ b/etc/kayobe/inventory/group_vars/all/growroot @@ -0,0 +1,3 @@ +--- +# Name of the LVM VG containing the root PV for ansible/growroot.yml +growroot_vg: "rootvg" From ed6eab94044d9fa5c0f85c78b1b57a79626b1fc9 Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Tue, 27 Feb 2024 10:59:26 +0100 Subject: [PATCH 043/128] Document update of IPA kernel URL This may be required when migrating pre-Ussuri deployments. --- doc/source/operations/rocky-linux-9.rst | 29 ++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/doc/source/operations/rocky-linux-9.rst b/doc/source/operations/rocky-linux-9.rst index c21c50f13..22b9323ae 100644 --- a/doc/source/operations/rocky-linux-9.rst +++ b/doc/source/operations/rocky-linux-9.rst @@ -45,11 +45,6 @@ The following patches have been **merged** to the downstream StackHPC ``stackhpc - https://review.opendev.org/c/openstack/kayobe/+/898563 (to fix ``kayobe overcloud deprovision``) - https://review.opendev.org/c/openstack/kayobe/+/898284 (if deployment predates Ussuri) - - - TODO: Put this into the procedure. - - **Must reprocess inspection data to update IPA kernel URL (see - release note)** - - https://review.opendev.org/c/openstack/kayobe/+/898777 - https://review.opendev.org/c/openstack/kayobe/+/898915 - https://review.opendev.org/c/openstack/kayobe/+/898905 @@ -119,6 +114,30 @@ The error from NetworkManager was: [1697192659.9611] keyfile: ipv4.routing-rules: invalid value for "routing-rule1": invalid value for "table" +Updating the IPA kernel URL +--------------------------- + +If the enrolment of the overcloud nodes in Bifrost predates Ussuri, the +``deploy_kernel`` configuration probably still points to the old +``ipa.vmlinuz`` file, resulting in the following error in Bifrost: + +.. code-block:: shell + + Failed to prepare to deploy: Validation of image href http://10.161.0.3:8080/ipa.vmlinuz failed, reason: Got HTTP code 404 instead of 200 in response to HEAD request. + +Switch the deployment kernel to ``ipa.kernel``: + +.. code-block:: shell + + (bifrost-deploy) OS_CLOUD=bifrost baremetal node set --driver-info deploy_kernel=http://:8080/ipa.kernel + +Alternatively, the node inspection data can be reprocessed, but this may erase +any manual configuration changes applied since the last inspection: + +.. code-block:: shell + + (bifrost-deploy) OS_CLOUD=bifrost baremetal introspection reprocess + Switching to iPXE ----------------- From e4f703673b4e5360be7e17dbc880d5d00dc67c60 Mon Sep 17 00:00:00 2001 From: Matt Crees Date: Thu, 8 Feb 2024 15:48:47 +0000 Subject: [PATCH 044/128] Override kolla_base_distro_version When running the kayobe-automation CI on a Zed/Antelope cloud running Ubuntu host images, the kolla tags to templated incorrectly. For example, a Jammy host gets ``kolla_base_distro_and_version = rocky-jammy``. This is caused by Kayobe's ``globals.yml`` template (``ansible/roles/kolla-ansible/templates/kolla/globals.yml``) setting ``kolla_base_distro_version`` based on Kayobe's variable of the sam name, which references ``os_version``. We can avoid this by explicitly using facts for the version in SKC's ``globals.yml`` template. --- etc/kayobe/kolla/globals.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/etc/kayobe/kolla/globals.yml b/etc/kayobe/kolla/globals.yml index b156f050a..7930677f8 100644 --- a/etc/kayobe/kolla/globals.yml +++ b/etc/kayobe/kolla/globals.yml @@ -10,6 +10,10 @@ enable_docker_repo: "{% raw %}{{ 'overcloud' not in group_names }}{% endraw %}" # This is necessary for os migrations where mixed clouds might be deployed kolla_base_distro: "{% raw %}{{ ansible_facts.distribution | lower }}{% endraw %}" +# Use facts so this is determined correctly when the control host OS differs +# from os_distribuition. +kolla_base_distro_version: "{% raw %}{{ ansible_facts.distribution_major_version }}{% raw %}" + # Convenience variable for base distro and version string. kolla_base_distro_and_version: "{% raw %}{{ kolla_base_distro }}-{{ kolla_base_distro_version }}{% endraw %}" From 0bc60fcfc626392a53000c1e04fd6869c9035299 Mon Sep 17 00:00:00 2001 From: Alex-Welsh Date: Wed, 21 Feb 2024 17:33:04 +0000 Subject: [PATCH 045/128] Add docs page for running Tempest --- doc/source/operations/index.rst | 1 + doc/source/operations/tempest.rst | 326 ++++++++++++++++++++++++++++++ 2 files changed, 327 insertions(+) create mode 100644 doc/source/operations/tempest.rst diff --git a/doc/source/operations/index.rst b/doc/source/operations/index.rst index f547f13bb..b151c46f4 100644 --- a/doc/source/operations/index.rst +++ b/doc/source/operations/index.rst @@ -13,3 +13,4 @@ This guide is for operators of the StackHPC Kayobe configuration project. rocky-linux-9 ubuntu-jammy secret-rotation + tempest diff --git a/doc/source/operations/tempest.rst b/doc/source/operations/tempest.rst new file mode 100644 index 000000000..82135adf9 --- /dev/null +++ b/doc/source/operations/tempest.rst @@ -0,0 +1,326 @@ +====================================== +Running Tempest with Kayobe Automation +====================================== + +Overview +======== + +This document describes how to configure and run `Tempest +`_ using `kayobe-automation +`_ from the ``.automation`` +submodule included with ``stackhpc-kayobe-config``. + +The best way of running Tempest is to use CI/CD workflows. Before proceeding, +consider whether it would be possible to use/set up a CI/CD workflow instead. +For more information, see the :doc:`CI/CD workflows page +`. + +The following guide will assume all commands are run from your +``kayobe-config`` root and the environment has been configured to run Kayobe +commands unless stated otherwise. + +Prerequisites +============= + +Installing Docker +----------------- + +``kayobe-automation`` runs in a container on the Ansible control host. This +means that Docker must be installed on the Ansible control host if it is not +already. + +.. warning:: + + Docker can cause networking issues when it is installed. By default, it + will create a bridge and change ``iptables`` rules. These can be disabled + by setting the following in ``/etc/docker/daemon.json``: + + .. code-block:: json + + { + "bridge": "none", + "iptables": false + } + + The bridge is the most common cause of issues and is *usually* safe to + disable. Disabling the ``iptables`` rules will break any GitHub actions + runners running on the host. + +To install Docker on Ubuntu: + +.. code-block:: bash + + # Add Docker's official GPG key: + sudo apt-get update + sudo apt-get install ca-certificates curl + sudo install -m 0755 -d /etc/apt/keyrings + sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc + sudo chmod a+r /etc/apt/keyrings/docker.asc + + # Add the repository to Apt sources: + echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ + $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ + sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + sudo apt-get update + sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + +Installing Docker on CentOS/Rocky: + +.. code-block:: bash + + sudo dnf install -y dnf-utils + sudo dnf-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo + sudo dnf install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + +Ensure Docker is running & enabled: + +.. code-block:: bash + + sudo systemctl start docker + sudo systemctl enable docker + +The Docker ``buildx`` plugin must be installed. If you are using an existing +installation of docker, you may need to install it with: + +.. code-block:: bash + + sudo dnf/apt install docker-buildx-plugin + sudo docker buildx install + # or if that fails: + sudo docker plugin install buildx + +Building a Kayobe container +--------------------------- + +Build a Kayobe automation image: + +.. code-block:: bash + + git submodule init + git submodule update + # If running on Ubuntu, the fact cache can confuse Kayobe in the CentOS-based container + mv etc/kayobe/facts{,-old} + sudo DOCKER_BUILDKIT=1 docker build --file .automation/docker/kayobe/Dockerfile --tag kayobe:latest . + +Configuration +============= + +Kayobe automation configuration files are stored in the ``.automation.conf/`` +directory. It contains: + +- A script used to export environment variables for meta configuration of + Tempest - ``.automation.conf/config.sh``. +- Tempest configuration override files, stored in ``.automation.conf/tempest/`` + and conventionally named ``tempest.overrides.conf`` or + ``tempest-.overrides.conf``. +- Tempest load lists, stored in ``.automation.conf/tempest/load-lists``. +- Tempest skip lists, stored in ``.automation.conf/tempest/skip-lists``. + +config.sh +--------- + +``config.sh`` is a mandatory shell script, primarily used to export environment +variables for the meta configuration of Tempest. + +See: +https://github.com/stackhpc/docker-rally/blob/master/bin/rally-verify-wrapper.sh +for a full list of Tempest parameters that can be overridden. + +The most common variables to override are: + +- ``TEMPEST_CONCURRENCY`` - The maximum number of tests to run in parallel at + one time. Higher values are faster but increase the risk of timeouts. 1-2 is + safest in CI/Tenks/Multinode/AIO etc. 8-32 is typical in production. Default + value is 2. +- ``KAYOBE_AUTOMATION_TEMPEST_LOADLIST``: the filename of a load list in the + ``load-lists`` directory. Default value is ``default`` (symlink to refstack). +- ``KAYOBE_AUTOMATION_TEMPEST_SKIPLIST``: the filename of a load list in the + ``skip-lists`` directory. Default value is unset. +- ``TEMPEST_OPENRC``: The **contents** of an ``openrc.sh`` file, to be used by + Tempest to create resources on the cloud. Default is to read in the contents + of ``etc/kolla/public-openrc.sh``. + +tempest.overrides.conf +---------------------- + +Tempest uses a configuration file to define which tests are run and how to run +them. A full sample configuration file can be found `here +`_. Sensible +defaults exist for all values and in most situations, a blank +``*overrides.conf`` file will successfully run many tests. It will however also +skip many tests which may otherwise be appropriate to run. + +`Shakespeare `_ is a tool for +generating Tempest configuration files. It contains elements for different +cloud features, which can be combined to template out a detailed configuration +file. This is the best-practice approach. + +Below is an example of a manually generated file including many of the most +common overrides. It makes many assumptions about the environment, so make sure +you understand all the options before applying them. + +.. NOTE(upgrade): Microversions change for each release +.. code-block:: ini + + [openstack] + # Use a StackHPC-built image without a default password. + img_url=https://github.com/stackhpc/cirros/releases/download/20231206/cirros-d231206-x86_64-disk.img + + [auth] + # Expect unlimited quotas for CPU cores and RAM + compute_quotas = cores:-1,ram:-1 + + [compute] + # Required for migration testing + min_compute_nodes = 2 + # Required to test some API features + min_microversion = 2.1 + max_microversion = 2.90 + # Flavors for creating test servers and server resize. The ``alt`` flavor should be larger. + flavor_ref = + flavor_ref_alt = + volume_multiattach = true + + [compute-feature-enabled] + # Required for migration testing + resize = true + live_migration = true + block_migration_for_live_migration = false + volume_backed_live_migration = true + + [placement] + min_microversion = "1.0" + max_microversion = "1.39" + + [volume] + storage_protocol = ceph + # Required to test some API features + min_microversion = 3.0 + max_microversion = 3.68 + +Tempest configuration override files are stored in +``.automation.conf/tempest/``. The default file used is +``tempest.overrides.conf`` or ``tempest-.overrides.conf`` +depending on whether a Kayobe environment is enabled. This can be changed by +setting ``KAYOBE_AUTOMATION_TEMPEST_CONF_OVERRIDES`` to a different file path. +An ``overrides.conf`` file must be supplied, even if it is blank. + +Load Lists +---------- + +Load lists are a newline-separated list of tests to run. They are stored in +``.automation.conf/tempest/load-lists/``. The directory contains three objects +by default: + +- ``tempest-full`` - A complete list of all possible tests. +- ``platform.2022.11-test-list.txt`` - A reduced list of tests to match the + `Refstack `_ standard. +- ``default`` - A symlink to ``platform.2022.11-test-list.txt``. + +Test lists can be selected by changing ``KAYOBE_AUTOMATION_TEMPEST_LOADLIST`` +in ``config.sh``. The default value is ``default``, which symlinks to +``platform.2022.11-test-list.txt``. + +A common use case is to use the ``failed-tests`` list output from a previous +Tempest run as a load list, to retry the failed tests after making changes. + +Skip Lists +---------- + +Skip lists are a newline-separated list of tests to Skip. They are stored in +``.automation.conf/tempest/skip-lists/``. Each line consists of a pattern to +match against test names, and a string explaining why the test is being +skipped e.g. + +.. code-block:: + + tempest.scenario.test_network_basic_ops.TestNetworkBasicOps.test_subnet_details.*: "Cirros image doesn't have /var/run/udhcpc.eth0.pid" + +There is no requirement for a skip list, and none is selected by default. A +skip list can be selected by setting ``KAYOBE_AUTOMATION_TEMPEST_SKIPLIST`` in +``config.sh``. + +Tempest runner +-------------- + +While the Kayobe automation container is always deployed to the ansible control +host, the Tempest container is deployed to the host in the ``tempest_runner`` +group, which can be any host in the Kayobe inventory. The group should only +ever contain one host. The seed is usually used as the tempest runner however +it is also common to use the Ansible control host or an infrastructure VM. The +main requirement of the host is that it can reach the OpenStack API. + +Running Tempest +=============== + +Kayobe automation will need to SSH to the Tempest runner (even if they are on +the same host), so requires an SSH key exported as +``KAYOBE_AUTOMATION_SSH_PRIVATE_KEY`` e.g. + +.. code-block:: bash + + export KAYOBE_AUTOMATION_SSH_PRIVATE_KEY=$(cat ~/.ssh/id_rsa) + +Tempest outputs will be sent to the ``tempest-artifacts/`` directory. Create +one if it does not exist. + +.. code-block:: bash + + mkdir tempest-artifacts + +The contents of ``tempest-artifacts`` will be overwritten. Ensure any previous +test results have been copied away. + +The Tempest playbook is invoked through the Kayobe container using this +command from the base of the ``kayobe-config`` directory: + +.. code-block:: bash + + sudo -E docker run --detach -it --rm --network host -v $(pwd):/stack/kayobe-automation-env/src/kayobe-config -v $(pwd)/tempest-artifacts:/stack/tempest-artifacts -e KAYOBE_ENVIRONMENT -e KAYOBE_VAULT_PASSWORD -e KAYOBE_AUTOMATION_SSH_PRIVATE_KEY kayobe:latest /stack/kayobe-automation-env/src/kayobe-config/.automation/pipeline/tempest.sh -e ansible_user=stack + +By default, ``no_log`` is set to stop credentials from leaking. This can be +disabled by adding ``-e rally_no_sensitive_log=false`` to the end. + +To follow the progress of the Kayobe automation container, either remove +``--detach`` from the above command, or follow the docker logs of the +``kayobe`` container. + +To follow the progress of the Tempest tests themselves, follow the logs of the +``tempest`` container on the ``tempest_runner`` host. + +.. code-block:: bash + + ssh + sudo docker logs -f tempest + +Tempest will keep running until completion if the ``kayobe`` container is +stopped. The ``tempest`` container must be stopped manually. Doing so will +however stop test resources (such as networks, images, and VMs) from being +automatically cleaned up. They must instead be manually removed. They should be +clearly labeled with either rally or tempest in the name, often alongside some +randomly generated string. + +Outputs +------- + +Tempest outputs will be sent to the ``tempest-artifacts/`` directory. It +contain the following artifacts: + +- ``docker.log`` - The logs from the ``tempest`` docker container +- ``failed-tests`` - A simple list of tests that failed +- ``rally-junit.xml`` - An XML file listing all tests in the test list and + their status (skipped/succeeded/failed). Usually not useful. +- ``rally-verify-report.html`` - An HTML page with all test results including + an error trace for failed tests. It is often best to ``scp`` this file back + to your local machine to view it. This is the most user-friendly way to view + the test results, however can be awkward to host. +- ``rally-verify-report.json`` - A JSON blob with all test results including an + error trace for failed tests. It contains all the same data as the HTML + report but without formatting. +- ``stderr.log`` - The stderr log. Usually not useful. +- ``stdout.log`` - The stdout log. Usually not useful. +- ``tempest-load-list`` - The load list that Tempest was invoked with. +- ``tempest.log`` - Detailed logs from Tempest. Contains more data than the + ``verify`` reports, but can be difficult to parse. Useful for tracing specific + errors. From 3217a11baf32ecd6c634613fb336e287d04b3dc5 Mon Sep 17 00:00:00 2001 From: Matt Crees Date: Thu, 29 Feb 2024 11:07:42 +0000 Subject: [PATCH 046/128] Update etc/kayobe/kolla/globals.yml Co-authored-by: Mark Goddard --- etc/kayobe/kolla/globals.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/kayobe/kolla/globals.yml b/etc/kayobe/kolla/globals.yml index 7930677f8..91021deed 100644 --- a/etc/kayobe/kolla/globals.yml +++ b/etc/kayobe/kolla/globals.yml @@ -12,7 +12,7 @@ kolla_base_distro: "{% raw %}{{ ansible_facts.distribution | lower }}{% endraw % # Use facts so this is determined correctly when the control host OS differs # from os_distribuition. -kolla_base_distro_version: "{% raw %}{{ ansible_facts.distribution_major_version }}{% raw %}" +kolla_base_distro_version: "{% raw %}{{ ansible_facts.distribution_major_version }}{% endraw %}" # Convenience variable for base distro and version string. kolla_base_distro_and_version: "{% raw %}{{ kolla_base_distro }}-{{ kolla_base_distro_version }}{% endraw %}" From 0c2c43b4dcfbb4ac5239235d1e827a70a962a595 Mon Sep 17 00:00:00 2001 From: sd109 Date: Thu, 29 Feb 2024 09:40:04 +0000 Subject: [PATCH 047/128] Bump magnum-capi-helm version to latest --- etc/kayobe/kolla.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/kayobe/kolla.yml b/etc/kayobe/kolla.yml index 1a047f33c..8844a3cbd 100644 --- a/etc/kayobe/kolla.yml +++ b/etc/kayobe/kolla.yml @@ -415,7 +415,7 @@ kolla_build_blocks: magnum_base_footer: | RUN curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | head -n -1 | bash {% raw %} - {% set magnum_capi_packages = ['git+https://github.com/stackhpc/magnum-capi-helm.git@v0.1.0'] %} + {% set magnum_capi_packages = ['git+https://github.com/stackhpc/magnum-capi-helm.git@v0.10.0'] %} RUN {{ macros.install_pip(magnum_capi_packages | customizable("pip_packages")) }} {% endraw %} # Dict mapping image customization variable names to their values. From 85ed3b58c6f932336d77067e14a68c4bbbb67ad2 Mon Sep 17 00:00:00 2001 From: Jakub Darmach Date: Fri, 16 Feb 2024 10:24:46 +0100 Subject: [PATCH 048/128] Bump Magnum Bump magnum_tag - support for k8s 1.27 fc38 Calico installed with Helm: Tigera Operator --- etc/kayobe/kolla/globals.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/etc/kayobe/kolla/globals.yml b/etc/kayobe/kolla/globals.yml index 65e22705b..7386a67be 100644 --- a/etc/kayobe/kolla/globals.yml +++ b/etc/kayobe/kolla/globals.yml @@ -19,6 +19,10 @@ kayobe_image_tags: centos: yoga-20231107T165648 rocky: yoga-20231218T141822 ubuntu: yoga-20231107T165648 + magnum: + centos: yoga-20240214T151004 + rocky: yoga-20240214T151004 + ubuntu: yoga-20240214T151004 neutron: centos: yoga-20231114T125927 rocky: yoga-20240105T120257 From a291be12a87ee2501d42062c93c54c38bca871a7 Mon Sep 17 00:00:00 2001 From: scrungus Date: Thu, 29 Feb 2024 12:59:10 +0000 Subject: [PATCH 049/128] bump magnum tag --- etc/kayobe/kolla/globals.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/etc/kayobe/kolla/globals.yml b/etc/kayobe/kolla/globals.yml index 7386a67be..618e56ed8 100644 --- a/etc/kayobe/kolla/globals.yml +++ b/etc/kayobe/kolla/globals.yml @@ -20,9 +20,9 @@ kayobe_image_tags: rocky: yoga-20231218T141822 ubuntu: yoga-20231107T165648 magnum: - centos: yoga-20240214T151004 - rocky: yoga-20240214T151004 - ubuntu: yoga-20240214T151004 + centos: yoga-20240229T120519 + rocky: yoga-20240229T120519 + ubuntu: yoga-20240229T120519 neutron: centos: yoga-20231114T125927 rocky: yoga-20240105T120257 From 727fe74a6212ecd70ca83c8dabf18c238caf7240 Mon Sep 17 00:00:00 2001 From: scrungus Date: Thu, 29 Feb 2024 13:02:10 +0000 Subject: [PATCH 050/128] reno --- releasenotes/notes/bump-magnum-51e03a61ae8aa5a4.yaml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 releasenotes/notes/bump-magnum-51e03a61ae8aa5a4.yaml diff --git a/releasenotes/notes/bump-magnum-51e03a61ae8aa5a4.yaml b/releasenotes/notes/bump-magnum-51e03a61ae8aa5a4.yaml new file mode 100644 index 000000000..b28764800 --- /dev/null +++ b/releasenotes/notes/bump-magnum-51e03a61ae8aa5a4.yaml @@ -0,0 +1,3 @@ +--- +fixes: + - Updates Magnum CAPI Helm driver version to v0.10.0 From fc46ea4cec58d163357323747672ec6b31155a0e Mon Sep 17 00:00:00 2001 From: Jake Hutchinson Date: Thu, 29 Feb 2024 15:19:30 +0000 Subject: [PATCH 051/128] Fix os-capacity playbook crash on delegate_to --- etc/kayobe/ansible/deploy-os-capacity-exporter.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/etc/kayobe/ansible/deploy-os-capacity-exporter.yml b/etc/kayobe/ansible/deploy-os-capacity-exporter.yml index e5cde5676..8cff6a89e 100644 --- a/etc/kayobe/ansible/deploy-os-capacity-exporter.yml +++ b/etc/kayobe/ansible/deploy-os-capacity-exporter.yml @@ -1,15 +1,18 @@ --- -- hosts: monitoring +- name: Remove legacy os_exporter.cfg file + hosts: network gather_facts: false - tasks: - name: Ensure legacy os_exporter.cfg config file is deleted ansible.builtin.file: path: /etc/kolla/haproxy/services.d/os_exporter.cfg state: absent - delegate_to: network become: true +- name: Deploy os-capacity exporter + hosts: monitoring + gather_facts: false + tasks: - name: Create os-capacity directory ansible.builtin.file: path: /opt/kayobe/os-capacity/ From 3064fbea804a4d9fad26c003dbd9b0243d1acd08 Mon Sep 17 00:00:00 2001 From: Alex-Welsh Date: Tue, 27 Feb 2024 10:43:26 +0000 Subject: [PATCH 052/128] Improved AIO deployment script The automated AIO deployment script has been improved in several ways: The password check is now the first command to run Adds advice about running with non-lvm images The python3-venv package is installed on Ubuntu hosts The purge-command-not-found playbook is automatically run before host configuration --- .../environments/ci-aio/automated-setup.sh | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/etc/kayobe/environments/ci-aio/automated-setup.sh b/etc/kayobe/environments/ci-aio/automated-setup.sh index 9252bf35a..5129db015 100644 --- a/etc/kayobe/environments/ci-aio/automated-setup.sh +++ b/etc/kayobe/environments/ci-aio/automated-setup.sh @@ -2,30 +2,35 @@ set -eux -cat << EOF | sudo tee -a /etc/hosts -10.205.3.187 pulp-server pulp-server.internal.sms-cloud -EOF - -if sudo vgdisplay | grep -q lvm2; then - sudo pvresize $(sudo pvs --noheadings | head -n 1 | awk '{print $1}') - sudo lvextend -L 4G /dev/rootvg/lv_home -r || true - sudo lvextend -L 4G /dev/rootvg/lv_tmp -r || true -fi - BASE_PATH=~ KAYOBE_BRANCH=stackhpc/yoga KAYOBE_CONFIG_BRANCH=stackhpc/yoga +KAYOBE_AIO_LVM=true if [[ ! -f $BASE_PATH/vault-pw ]]; then echo "Vault password file not found at $BASE_PATH/vault-pw" exit 1 fi +if sudo vgdisplay | grep -q lvm2; then + sudo pvresize $(sudo pvs --noheadings | head -n 1 | awk '{print $1}') + sudo lvextend -L 4G /dev/rootvg/lv_home -r || true + sudo lvextend -L 4G /dev/rootvg/lv_tmp -r || true +elif $KAYOBE_AIO_LVM; then + echo "This environment is only designed for LVM images. If possible, switch to an LVM image. + To ignore this warning, set KAYOBE_AIO_LVM to false in this script." + exit 1 +fi + +cat << EOF | sudo tee -a /etc/hosts +10.205.3.187 pulp-server pulp-server.internal.sms-cloud +EOF + if type dnf; then sudo dnf -y install git else sudo apt update - sudo apt -y install gcc git libffi-dev python3-dev python-is-python3 + sudo apt -y install gcc git libffi-dev python3-dev python-is-python3 python3-venv fi cd $BASE_PATH @@ -35,6 +40,11 @@ pushd src [[ -d kayobe-config ]] || git clone https://github.com/stackhpc/stackhpc-kayobe-config kayobe-config -b $KAYOBE_CONFIG_BRANCH popd +if ! sudo vgdisplay | grep -q lvm2; then + rm $BASE_PATH/src/kayobe-config/etc/kayobe/environments/ci-aio/inventory/group_vars/controllers/lvm.yml + sed -i -e '/controller_lvm_groups/,+2d' $BASE_PATH/src/kayobe-config/etc/kayobe/environments/ci-aio/controllers.yml +fi + mkdir -p venvs pushd venvs if [[ ! -d kayobe ]]; then @@ -68,7 +78,7 @@ source kayobe-env --environment ci-aio kayobe control host bootstrap -kayobe playbook run etc/kayobe/ansible/growroot.yml +kayobe playbook run etc/kayobe/ansible/growroot.yml etc/kayobe/ansible/purge-command-not-found.yml kayobe overcloud host configure From 47818f562f72d103097d31334a3a6567cfbd4db8 Mon Sep 17 00:00:00 2001 From: scrungus <33693738+scrungus@users.noreply.github.com> Date: Fri, 1 Mar 2024 09:48:55 +0000 Subject: [PATCH 053/128] magnum_tag --- etc/kayobe/kolla/globals.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/etc/kayobe/kolla/globals.yml b/etc/kayobe/kolla/globals.yml index 618e56ed8..419b1ba72 100644 --- a/etc/kayobe/kolla/globals.yml +++ b/etc/kayobe/kolla/globals.yml @@ -33,6 +33,7 @@ kayobe_image_tags: ubuntu: yoga-20231103T161400 cloudkitty_tag: "{% raw %}{{ kayobe_image_tags['cloudkitty'][kolla_base_distro] }}{% endraw %}" +magnum_tag: "{% raw %}{{ kayobe_image_tags['magnum'][kolla_base_distro] }}{% endraw %}" neutron_tag: "{% raw %}{{ kayobe_image_tags['neutron'][kolla_base_distro] }}{% endraw %}" nova_tag: "{% raw %}{{ kayobe_image_tags['nova'][kolla_base_distro] }}{% endraw %}" opensearch_tag: yoga-20231219T221916 From 564a20a3ba091feeec304f95fdfd9d5dfaa42edf Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Fri, 1 Mar 2024 10:03:06 +0000 Subject: [PATCH 054/128] docs: Update microversions in tempest.conf example for Zed --- doc/source/operations/tempest.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/source/operations/tempest.rst b/doc/source/operations/tempest.rst index 82135adf9..a3bd4ac1c 100644 --- a/doc/source/operations/tempest.rst +++ b/doc/source/operations/tempest.rst @@ -176,7 +176,7 @@ you understand all the options before applying them. min_compute_nodes = 2 # Required to test some API features min_microversion = 2.1 - max_microversion = 2.90 + max_microversion = 2.93 # Flavors for creating test servers and server resize. The ``alt`` flavor should be larger. flavor_ref = flavor_ref_alt = @@ -190,14 +190,14 @@ you understand all the options before applying them. volume_backed_live_migration = true [placement] - min_microversion = "1.0" - max_microversion = "1.39" + min_microversion = 1.0 + max_microversion = 1.39 [volume] storage_protocol = ceph # Required to test some API features min_microversion = 3.0 - max_microversion = 3.68 + max_microversion = 3.70 Tempest configuration override files are stored in ``.automation.conf/tempest/``. The default file used is From 068b668acdcf105d6d0037ecc65ec91fc20e137a Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Fri, 1 Mar 2024 10:04:26 +0000 Subject: [PATCH 055/128] Update magnum container images for Zed --- etc/kayobe/kolla-image-tags.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/etc/kayobe/kolla-image-tags.yml b/etc/kayobe/kolla-image-tags.yml index 5cd94a2c3..a264fdbf1 100644 --- a/etc/kayobe/kolla-image-tags.yml +++ b/etc/kayobe/kolla-image-tags.yml @@ -6,6 +6,9 @@ kolla_image_tags: openstack: rocky-9: zed-rocky-9-20240202T105829 ubuntu-jammy: zed-ubuntu-jammy-20240129T151534 + magnum: + rocky-9: zed-rocky-9-20240301T100039 + ubuntu-jammy: zed-ubuntu-jammy-20240301T100039 neutron: rocky-9: zed-rocky-9-20240202T141530 ubuntu-jammy: zed-ubuntu-jammy-20240202T143208 From 21dbda597e73ede719d07337915ce17b0ecf9e65 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Fri, 1 Mar 2024 10:10:40 +0000 Subject: [PATCH 056/128] docs: Update microversions in tempest.conf example for 2023.1 --- doc/source/operations/tempest.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/operations/tempest.rst b/doc/source/operations/tempest.rst index a3bd4ac1c..f61933198 100644 --- a/doc/source/operations/tempest.rst +++ b/doc/source/operations/tempest.rst @@ -176,7 +176,7 @@ you understand all the options before applying them. min_compute_nodes = 2 # Required to test some API features min_microversion = 2.1 - max_microversion = 2.93 + max_microversion = 2.95 # Flavors for creating test servers and server resize. The ``alt`` flavor should be larger. flavor_ref = flavor_ref_alt = From 2d97d59ba7f237a095293dc6872ed89841fcaffc Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Fri, 1 Mar 2024 10:11:10 +0000 Subject: [PATCH 057/128] Update magnum container images for 2023.1 Co-Authored-By: Scott Davidson --- etc/kayobe/kolla-image-tags.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/etc/kayobe/kolla-image-tags.yml b/etc/kayobe/kolla-image-tags.yml index ab46cb4cc..9358037cf 100644 --- a/etc/kayobe/kolla-image-tags.yml +++ b/etc/kayobe/kolla-image-tags.yml @@ -12,6 +12,9 @@ kolla_image_tags: letsencrypt: rocky-9: 2023.1-rocky-9-20240205T162323 ubuntu-jammy: 2023.1-ubuntu-jammy-20240221T133905 + magnum: + rocky-9: 2023.1-rocky-9-20240229T103619 + ubuntu-jammy: 2023.1-ubuntu-jammy-20240229T103619 neutron: rocky-9: 2023.1-rocky-9-20240202T145927 ubuntu-jammy: 2023.1-ubuntu-jammy-20240221T103817 From b0743ed2c94d3a54d4071c9cc05033c991cedb03 Mon Sep 17 00:00:00 2001 From: Matt Crees Date: Fri, 1 Mar 2024 10:48:33 +0000 Subject: [PATCH 058/128] Correctly map kolla_base_distro_version --- etc/kayobe/kolla/globals.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/kayobe/kolla/globals.yml b/etc/kayobe/kolla/globals.yml index 91021deed..83bfd9b74 100644 --- a/etc/kayobe/kolla/globals.yml +++ b/etc/kayobe/kolla/globals.yml @@ -12,7 +12,7 @@ kolla_base_distro: "{% raw %}{{ ansible_facts.distribution | lower }}{% endraw % # Use facts so this is determined correctly when the control host OS differs # from os_distribuition. -kolla_base_distro_version: "{% raw %}{{ ansible_facts.distribution_major_version }}{% endraw %}" +kolla_base_distro_version: "{% raw %}{{ kolla_base_distro_version_default_map[kolla_base_distro] }}{% endraw %}" # Convenience variable for base distro and version string. kolla_base_distro_and_version: "{% raw %}{{ kolla_base_distro }}-{{ kolla_base_distro_version }}{% endraw %}" From a3a8c101f9ef90bee23a9ea44402d1362b9e5d91 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Fri, 1 Mar 2024 11:42:22 +0000 Subject: [PATCH 059/128] Replace references to CentOS with Rocky Linux --- doc/source/contributor/environments/aufn-ceph.rst | 2 +- doc/source/contributor/environments/ci-builder.rst | 2 +- doc/source/operations/tempest.rst | 4 ++-- .../environments/aufn-ceph/configure-local-networking.sh | 2 +- etc/kayobe/environments/aufn-ceph/seed-hypervisor.yml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/source/contributor/environments/aufn-ceph.rst b/doc/source/contributor/environments/aufn-ceph.rst index 5fe07b86f..9578efb48 100644 --- a/doc/source/contributor/environments/aufn-ceph.rst +++ b/doc/source/contributor/environments/aufn-ceph.rst @@ -14,7 +14,7 @@ This environment creates a Universe-from-nothing_-style deployment of Kayobe con Prerequisites ============= -* a baremetal node with at least 64GB of RAM running CentOS Stream 8 (or Ubuntu) +* a baremetal node with at least 64GB of RAM running Rocky Linux 9 or Ubuntu Jammy. * access to the test pulp server on SMS lab diff --git a/doc/source/contributor/environments/ci-builder.rst b/doc/source/contributor/environments/ci-builder.rst index f0a6f0ee9..5cbc3371e 100644 --- a/doc/source/contributor/environments/ci-builder.rst +++ b/doc/source/contributor/environments/ci-builder.rst @@ -25,7 +25,7 @@ Access the host via SSH. Install package dependencies. -On CentOS: +On Rocky Linux: .. parsed-literal:: diff --git a/doc/source/operations/tempest.rst b/doc/source/operations/tempest.rst index a3bd4ac1c..ea8503626 100644 --- a/doc/source/operations/tempest.rst +++ b/doc/source/operations/tempest.rst @@ -65,7 +65,7 @@ To install Docker on Ubuntu: sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -Installing Docker on CentOS/Rocky: +Installing Docker on Rocky: .. code-block:: bash @@ -99,7 +99,7 @@ Build a Kayobe automation image: git submodule init git submodule update - # If running on Ubuntu, the fact cache can confuse Kayobe in the CentOS-based container + # If running on Ubuntu, the fact cache can confuse Kayobe in the Rocky-based container mv etc/kayobe/facts{,-old} sudo DOCKER_BUILDKIT=1 docker build --file .automation/docker/kayobe/Dockerfile --tag kayobe:latest . diff --git a/etc/kayobe/environments/aufn-ceph/configure-local-networking.sh b/etc/kayobe/environments/aufn-ceph/configure-local-networking.sh index ab3602d2a..c22bbd518 100755 --- a/etc/kayobe/environments/aufn-ceph/configure-local-networking.sh +++ b/etc/kayobe/environments/aufn-ceph/configure-local-networking.sh @@ -43,7 +43,7 @@ if ! sudo ip l show brcloud >/dev/null 2>&1; then sudo ip l set brcloud up fi -# On CentOS 8, bridges without a port are DOWN, which causes network +# On Rocky Linux, bridges without a port are DOWN, which causes network # configuration to fail. Add a dummy interface and plug it into the bridge. for i in mgmt prov cloud; do if ! sudo ip l show dummy-$i >/dev/null 2>&1; then diff --git a/etc/kayobe/environments/aufn-ceph/seed-hypervisor.yml b/etc/kayobe/environments/aufn-ceph/seed-hypervisor.yml index 6a1b7ffdf..2f288f030 100644 --- a/etc/kayobe/environments/aufn-ceph/seed-hypervisor.yml +++ b/etc/kayobe/environments/aufn-ceph/seed-hypervisor.yml @@ -10,5 +10,5 @@ seed_hypervisor_extra_network_interfaces: - "{{ public_net_name }}" - "{{ external_net_names[0] }}" -# Workaround change to cloud-user default login name on CentOS-Stream8 +# Workaround change to cloud-user default login name on Rocky Linux seed_hypervisor_bootstrap_user: "{{ lookup('env', 'USER') }}" From b83cec2ba3cd27ccf6467da1cb34194af4cf9c68 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Fri, 1 Mar 2024 11:43:22 +0000 Subject: [PATCH 060/128] docs: Add BASE_IMAGE build-arg for kayobe image build A Rocky Linux 9 base image is required for Zed onwards. --- doc/source/operations/tempest.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/operations/tempest.rst b/doc/source/operations/tempest.rst index ea8503626..a5991097c 100644 --- a/doc/source/operations/tempest.rst +++ b/doc/source/operations/tempest.rst @@ -101,7 +101,7 @@ Build a Kayobe automation image: git submodule update # If running on Ubuntu, the fact cache can confuse Kayobe in the Rocky-based container mv etc/kayobe/facts{,-old} - sudo DOCKER_BUILDKIT=1 docker build --file .automation/docker/kayobe/Dockerfile --tag kayobe:latest . + sudo DOCKER_BUILDKIT=1 docker build --build-arg BASE_IMAGE=rockylinux:9 --file .automation/docker/kayobe/Dockerfile --tag kayobe:latest . Configuration ============= From 100f544225fdbe48802f77de569d7d127cb865f4 Mon Sep 17 00:00:00 2001 From: Alex-Welsh Date: Tue, 5 Mar 2024 15:35:03 +0000 Subject: [PATCH 061/128] Fix libvirt error for tenks on Rocky Linux 9 --- etc/kayobe/environments/aufn-ceph/tenks.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/etc/kayobe/environments/aufn-ceph/tenks.yml b/etc/kayobe/environments/aufn-ceph/tenks.yml index 9b0e9e9f4..25eac0374 100644 --- a/etc/kayobe/environments/aufn-ceph/tenks.yml +++ b/etc/kayobe/environments/aufn-ceph/tenks.yml @@ -87,3 +87,9 @@ bridge_type: linuxbridge # No placement service. wait_for_placement: false + +# NOTE(priteau): Disable libvirt_vm_trust_guest_rx_filters, which when enabled +# triggers the following errors when booting baremetal instances with Tenks on +# Libvirt 9: Cannot set interface flags on 'macvtap1': Value too large for +# defined data type +libvirt_vm_trust_guest_rx_filters: false From 4a20e1149ccb0cea2d7a50f73bb5db2eb2afd441 Mon Sep 17 00:00:00 2001 From: Matt Crees Date: Wed, 6 Mar 2024 09:26:40 +0000 Subject: [PATCH 062/128] Update etc/kayobe/environments/aufn-ceph/a-universe-from-nothing.sh --- etc/kayobe/environments/aufn-ceph/a-universe-from-nothing.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/kayobe/environments/aufn-ceph/a-universe-from-nothing.sh b/etc/kayobe/environments/aufn-ceph/a-universe-from-nothing.sh index 03cd23439..e594ea388 100755 --- a/etc/kayobe/environments/aufn-ceph/a-universe-from-nothing.sh +++ b/etc/kayobe/environments/aufn-ceph/a-universe-from-nothing.sh @@ -87,7 +87,7 @@ kayobe seed vm provision kayobe seed host configure # Deploy local pulp server as a container on the seed VM -kayobe seed service deploy --tags seed-deploy-containers --kolla-tags +kayobe seed service deploy --tags seed-deploy-containers --kolla-tags none # Deploying the seed restarts networking interface, run configure-local-networking.sh again to re-add routes. $KAYOBE_CONFIG_PATH/environments/$KAYOBE_ENVIRONMENT/configure-local-networking.sh From 49646457e27fee3aacdec392a627d9c729cf3a54 Mon Sep 17 00:00:00 2001 From: Michal Nasiadka Date: Wed, 6 Mar 2024 11:25:08 +0100 Subject: [PATCH 063/128] Switch ansible-modules-hashivault back to upstream 5.2.1 version was released with shebang fix [1]. [1]: https://github.com/TerryHowe/ansible-modules-hashivault/commit/f1d30f18193562c30ff8e3d63ec2f4d1e2745f47 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 64f49178a..c2792b36b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ kayobe@git+https://github.com/stackhpc/kayobe@stackhpc/2023.1 -ansible-modules-hashivault@git+https://github.com/stackhpc/ansible-modules-hashivault@stackhpc-py39 +ansible-modules-hashivault>=5.2.1 jmespath From be1b504c68feee30019e73b7f705cb3d1203afe3 Mon Sep 17 00:00:00 2001 From: Michal Nasiadka Date: Wed, 6 Mar 2024 11:35:14 +0100 Subject: [PATCH 064/128] Switch ansible-modules-hashivault back to upstream 5.2.1 version was released with shebang fix [1]. [1]: TerryHowe/ansible-modules-hashivault@f1d30f1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2089c4b3f..ba1a14f12 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ kayobe@git+https://github.com/stackhpc/kayobe@stackhpc/yoga ansible-modules-hashivault@git+https://github.com/stackhpc/ansible-modules-hashivault@stackhpc;python_version < "3.8" -ansible-modules-hashivault@git+https://github.com/stackhpc/ansible-modules-hashivault@stackhpc-py39;python_version >= "3.8" +ansible-modules-hashivault>=5.2.1;python_version >= "3.8" jmespath From ecd1dc9e00256c7388d18a8d6e8fcad948f3908d Mon Sep 17 00:00:00 2001 From: Alex-Welsh Date: Wed, 6 Mar 2024 13:13:01 +0000 Subject: [PATCH 065/128] Remove kolla-limit from host configure example --- doc/source/configuration/cephadm.rst | 2 +- doc/source/contributor/package-updates.rst | 2 +- doc/source/operations/upgrading.rst | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/configuration/cephadm.rst b/doc/source/configuration/cephadm.rst index c9607d600..d7f41c91d 100644 --- a/doc/source/configuration/cephadm.rst +++ b/doc/source/configuration/cephadm.rst @@ -448,7 +448,7 @@ Configure the Ceph hosts: .. code:: bash - kayobe overcloud host configure --limit storage --kolla-limit storage + kayobe overcloud host configure --limit storage Ceph deployment --------------- diff --git a/doc/source/contributor/package-updates.rst b/doc/source/contributor/package-updates.rst index 51473b860..5577fce65 100644 --- a/doc/source/contributor/package-updates.rst +++ b/doc/source/contributor/package-updates.rst @@ -102,7 +102,7 @@ For Rocky Linux 9, bump the snapshot versions in /etc/yum/repos.d with: .. code-block:: console - kayobe overcloud host configure -t dnf -kt none + kayobe overcloud host configure -t dnf Install new packages: diff --git a/doc/source/operations/upgrading.rst b/doc/source/operations/upgrading.rst index a5d781ddc..4cb7c9852 100644 --- a/doc/source/operations/upgrading.rst +++ b/doc/source/operations/upgrading.rst @@ -948,7 +948,7 @@ least start with a small number of hosts.: .. code-block:: console - kayobe overcloud host configure --limit --kolla-limit + kayobe overcloud host configure --limit Alternatively, to apply the configuration to all hosts: From 2a4211f5ed2bed2b1f98b921fbdad44e1eb2810a Mon Sep 17 00:00:00 2001 From: Jake Hutchinson Date: Wed, 7 Feb 2024 13:34:42 +0000 Subject: [PATCH 066/128] Fix certificate path for os-capacity haproxy in Antelope --- etc/kayobe/kolla/config/haproxy/services.d/os_capacity.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/kayobe/kolla/config/haproxy/services.d/os_capacity.cfg b/etc/kayobe/kolla/config/haproxy/services.d/os_capacity.cfg index 4326265ca..cebacb98b 100644 --- a/etc/kayobe/kolla/config/haproxy/services.d/os_capacity.cfg +++ b/etc/kayobe/kolla/config/haproxy/services.d/os_capacity.cfg @@ -7,7 +7,7 @@ frontend os_capacity_frontend option forwardfor http-request set-header X-Forwarded-Proto https if { ssl_fc } {% if kolla_enable_tls_internal | bool %} - bind {{ kolla_internal_vip_address }}:9090 ssl crt /etc/haproxy/haproxy-internal.pem + bind {{ kolla_internal_vip_address }}:9090 ssl crt /etc/haproxy/certificates/haproxy-internal.pem {% else %} bind {{ kolla_internal_vip_address }}:9090 {% endif %} From 1455b28f9c9670268ec00c677a9ef239daad809c Mon Sep 17 00:00:00 2001 From: technowhizz <7688823+technowhizz@users.noreply.github.com> Date: Thu, 7 Mar 2024 15:09:47 +0000 Subject: [PATCH 067/128] Update upgrading docs to include Opensearch issue Adding the known issue with Opensearch to the upgrading docs from the 2023.1 docs. --- doc/source/operations/upgrading.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/doc/source/operations/upgrading.rst b/doc/source/operations/upgrading.rst index 506e966c5..53df5aef2 100644 --- a/doc/source/operations/upgrading.rst +++ b/doc/source/operations/upgrading.rst @@ -106,6 +106,24 @@ Known issues * The OVN sync repair tool removes metadata ports, breaking OVN load balancers. See `LP#2038091 `__. +* If you run ``kayobe overcloud service upgrade`` twice, it will cause shard + allocation to be disabled in OpenSearch. See `LP#2049512 + `__ for details. + + You can check if this is affecting your system with the following command. If + ``transient.cluster.routing.allocation.enable=none`` is present, shard + allocation is disabled. + + .. code-block:: console + + curl http://:9200/_cluster/settings + + For now, the easiest way to fix this is to turn allocation back on: + + .. code-block:: console + + curl -X PUT http://:9200/_cluster/settings -H 'Content-Type:application/json' -d '{"transient":{"cluster":{"routing":{"allocation":{"enable":"all"}}}}}' + Security baseline ================= From 2940c8d1dc71b9b9b1ac9486e8d5c0b9abcb8101 Mon Sep 17 00:00:00 2001 From: technowhizz <7688823+technowhizz@users.noreply.github.com> Date: Thu, 7 Mar 2024 15:58:12 +0000 Subject: [PATCH 068/128] Move release note to correct place --- .../notes/fixes-osd-size-summary-9924ef4aac61d2b6.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {etc/kayobe/kolla/config/grafana/dashboards/ceph/releasenotes => releasenotes}/notes/fixes-osd-size-summary-9924ef4aac61d2b6.yaml (100%) diff --git a/etc/kayobe/kolla/config/grafana/dashboards/ceph/releasenotes/notes/fixes-osd-size-summary-9924ef4aac61d2b6.yaml b/releasenotes/notes/fixes-osd-size-summary-9924ef4aac61d2b6.yaml similarity index 100% rename from etc/kayobe/kolla/config/grafana/dashboards/ceph/releasenotes/notes/fixes-osd-size-summary-9924ef4aac61d2b6.yaml rename to releasenotes/notes/fixes-osd-size-summary-9924ef4aac61d2b6.yaml From 329197abbbf0a30d52de45a90224b6947a09a528 Mon Sep 17 00:00:00 2001 From: Doug Szumski Date: Wed, 6 Mar 2024 15:32:25 +0000 Subject: [PATCH 069/128] Add Nova Compute Ironic failover procedure Document: - Moving from multiple instances to a single instance - How to re-deploy the service Config changes: - Prompt users to set a static nova-compute-ironic 'host' name. --- doc/source/operations/index.rst | 7 +- doc/source/operations/nova-compute-ironic.rst | 211 ++++++++++++++++++ .../config/nova/nova-compute-ironic.conf | 4 + ...-ironic-failover-doc-a0c4f45b1fb48c4a.yaml | 12 + 4 files changed, 231 insertions(+), 3 deletions(-) create mode 100644 doc/source/operations/nova-compute-ironic.rst create mode 100644 etc/kayobe/kolla/config/nova/nova-compute-ironic.conf create mode 100644 releasenotes/notes/add-nova-compute-ironic-failover-doc-a0c4f45b1fb48c4a.yaml diff --git a/doc/source/operations/index.rst b/doc/source/operations/index.rst index 38acb60ff..39f1bb847 100644 --- a/doc/source/operations/index.rst +++ b/doc/source/operations/index.rst @@ -7,9 +7,10 @@ This guide is for operators of the StackHPC Kayobe configuration project. .. toctree:: :maxdepth: 1 - upgrading - rabbitmq - octavia hotfix-playbook + nova-compute-ironic + octavia + rabbitmq secret-rotation tempest + upgrading diff --git a/doc/source/operations/nova-compute-ironic.rst b/doc/source/operations/nova-compute-ironic.rst new file mode 100644 index 000000000..e139fa050 --- /dev/null +++ b/doc/source/operations/nova-compute-ironic.rst @@ -0,0 +1,211 @@ +=================== +Nova Compute Ironic +=================== + +This section describes the deployment of the OpenStack Nova Compute +Ironic service. The Nova Compute Ironic service is used to integrate +OpenStack Ironic into Nova as a 'hypervisor' driver. The end users of Nova +can then deploy and manage baremetal hardware, in a similar way to VMs. + +High Availability (HA) +====================== + +The OpenStack Nova Compute service is designed to be installed once on every +hypervisor in an OpenStack deployment. In this configuration, it makes little +sense to run additional service instances. Even if you wanted to, it's not +supported by design. This pattern breaks down with the Ironic baremetal +service, which must run on the OpenStack control plane. It is not feasible +to have a 1:1 mapping of Nova Compute Ironic services to baremetal nodes. + +The obvious HA solution is to run multiple instances of Nova Compute Ironic +on the control plane, so that if one fails, the others can take over. However, +due to assumptions long baked into the Nova source code, this is not trivial. +The HA feature provided by the Nova Compute Ironic service has proven to be +unstable, and the direction upstream is to switch to an active/passive +solution [1]. + +However, challenges still exist with the active/passive solution. Since the +Nova Compute Ironic HA feature is 'always on', one must ensure that only a +single instance (per Ironic conductor group) is ever running. It is not +possible to simply put multiple service instances behind HAProxy and use the +active/passive mode. + +Such problems are commonly solved with a technology such as Pacemaker, or in +the modern world, with a container orchestration engine such as Kubernetes. +Kolla Ansible provides neither, because in general it doesn't need to. Its +goal is simplicity. + +The interim solution is to therefore run a single Nova Compute Ironic +service. If the service goes down, remedial action must be taken before +Ironic nodes can be managed. In many environments the loss of the Ironic +API for short periods is acceptable, providing that it can be easily +resurrected. The purpose of this document is to faciliate that. + +TODO: Add caveats about new sharding mode (not covered here). + +Optimal configuration of Nova Compute Ironic +============================================ + +Determine the current configuration for the site. How many Nova Compute +Ironic instances are running on the control plane? + +.. code-block:: console + + $ openstack compute service list + +Typically you will see either three or one. By default the host will +marked with a postfix, eg. ``controller1-ironic``. If you find more than +one, you will need to remove some instances. You must complete the +following section. + +Moving from multiple Nova Compute Instances to a single instance +---------------------------------------------------------------- + +1. Decide where the single instance should run. Typically, this will be + one of the three control plane hosts. Once you have chosen, set + the following variable in ``etc/kayobe/nova.yml``. Here we have + picked ``controller1``. + + .. code-block:: console + + kolla_nova_compute_ironic_host: controller1 + +2. Ensure that you have organised a maintenance window, during which + there will be no Ironic operations. You will be breaking the Ironic + API. + +3. Perform a database backup. + + .. code-block:: console + + $ kayobe overcloud database backup -vvv + + Check the output of the command, and locate the backup files. + +4. Identify baremetal nodes associated with Nova Compute Ironic instances + that will be removed. You don't need to do anything with these + specifically, it's just for reference later. For example: + + .. code-block:: console + + $ openstack baremetal node list --long -c "Instance Info" | grep controller3-ironic | wc -l + 61 + $ openstack baremetal node list --long -c "Instance Info" | grep controller2-ironic | wc -l + 35 + $ openstack baremetal node list --long -c "Instance Info" | grep controller1-ironic | wc -l + 55 + +5. Disable the redundant Nova Compute Ironic services: + + .. code-block:: console + + $ openstack compute service set controller3-ironic nova-compute --disable + $ openstack compute service set controller2-ironic nova-compute --disable + +6. Delete the redundant Nova Compute Ironic services. You will need the service + ID. For example: + + .. code-block:: console + + $ ID=$(openstack compute service list | grep foo | awk '{print $2}') + $ openstack compute service delete --os-compute-api-version 2.53 $ID + + In older releases, you may hit a bug where the service can't be deleted if it + is not managing any instances. In this case just move on and leave the service + disabled. Eg. + + .. code-block:: console + + $ openstack compute service delete --os-compute-api-version 2.53 c993b57e-f60c-4652-8328-5fb0e17c99c0 + Failed to delete compute service with ID 'c993b57e-f60c-4652-8328-5fb0e17c99c0': HttpException: 500: Server Error for url: + https://acme.pl-2.internal.hpc.is:8774/v2.1/os-services/c993b57e-f60c-4652-8328-5fb0e17c99c0, Unexpected API Error. + Please report this at http://bugs.launchpad.net/nova/ and attach the Nova API log if possible. + +7. Remove the Docker containers for the redundant Nova Compute Ironic services: + + .. code-block:: console + + $ ssh controller2 sudo docker rm -f nova_compute_ironic + $ ssh controller3 sudo docker rm -f nova_compute_ironic + +8. Ensure that all Ironic nodes are using the single remaining Nova Compute + Ironic instance. Eg. Baremetal nodes in use by compute instances will not + fail over to the remaining Nova Compute Ironic service. Here, the active + service is running on ``controller1``: + + .. code-block:: console + + $ ssh controller1 + $ sudo docker exec -it mariadb mysql -u nova -p$(sudo grep 'mysql+pymysql://nova:' /etc/kolla/nova-api/nova.conf | awk -F'[:,@]' '{print $3}') + $ MariaDB [(none)]> use nova; + + Proceed with caution. It is good practise to update one record first: + + .. code-block:: console + + $ MariaDB [nova]> update instances set host='controller1-ironic' where uuid=0 and host='controller3-ironic' limit 1; + Query OK, 1 row affected (0.002 sec) + Rows matched: 1 Changed: 1 Warnings: 0 + + At this stage you should go back to step 4 and check that the numbers have + changed as expected. When you are happy, update remaining records for all + services which have been removed: + + .. code-block:: console + + $ MariaDB [nova]> update instances set host='controller1-ironic' where deleted=0 and host='controller3-ironic'; + Query OK, 59 rows affected (0.009 sec) + Rows matched: 59 Changed: 59 Warnings: 0 + $ MariaDB [nova]> update instances set host='controller1-ironic' where deleted=0 and host='controller2-ironic'; + Query OK, 35 rows affected (0.003 sec) + Rows matched: 35 Changed: 35 Warnings: 0 + +9. Repeat step 4. Verify that all Ironic nodes are using the single remaining + Nova Compute Ironic instance. + + +Making it easy to re-deploy Nova Compute Ironic +----------------------------------------------- + +In the previous section we saw that at any given time, a baremetal node is +associated with a single Nova Compute Ironic instance. At this stage, assuming +that you have diligently followed the instructions, you are in the situation +where all Ironic baremetal nodes are managed by a single Nova Compute Ironic +instance. If this service goes down, you will not be able to manage /any/ +baremetal nodes. + +By default, the single remaining Nova Compute Ironic instance will be named +after the host on which it is deployed. The host name is passed to the Nova +Compute Ironic instance via the default section of the ``nova.conf`` file, +using the field: ``host``. + +If you wish to re-deploy this instance, for example because the original host +was permanently mangled in the World Server Throwing Championship [2], you +must ensure that the new instance has the same name as the old one. Simply +setting ``kolla_nova_compute_ironic_host`` to another controller and +re-deploying the service is not enough; the new instance will be named after +the new host. + +To work around this you should set the ``host`` field in ``nova.conf`` to a +constant, such that the new Nova Compute Ironic instance comes up with the +same name as the one it replaces. + +For example, if the original instance resides on ``controller1``, then set the +following in ``etc/kayobe/nova.yml``: + +.. code-block:: console + + kolla_nova_compute_ironic_static_host_name: controller1-ironic + +Note that an ``-ironic`` postfix is added to the hostname. This comes from +a convention in Kolla Ansible. It is worth making this change ahead of time, +even if you don't need to immediately re-deploy the service. + +It is also possible to use an arbitrary ``host`` name, but you will need +to edit the database again. That is an optional exercise left for the reader. +See [1] for further details. + +TODO: Investigate KA bug with assumption about host field. + +[1] https://specs.openstack.org/openstack/nova-specs/specs/2024.1/approved/ironic-shards.html#migrate-from-peer-list-to-shard-key +[2] https://www.cloudfest.com/world-server-throwing-championship diff --git a/etc/kayobe/kolla/config/nova/nova-compute-ironic.conf b/etc/kayobe/kolla/config/nova/nova-compute-ironic.conf new file mode 100644 index 000000000..9f6db7a55 --- /dev/null +++ b/etc/kayobe/kolla/config/nova/nova-compute-ironic.conf @@ -0,0 +1,4 @@ +{% if kolla_enable_ironic|bool and kolla_nova_compute_ironic_host is not none %} +[DEFAULT] +host = {{ kolla_nova_compute_ironic_static_host_name | mandatory('You must set a static host name to help with service failover. See the operations documentation, Ironic section.') }} +{% endif %} diff --git a/releasenotes/notes/add-nova-compute-ironic-failover-doc-a0c4f45b1fb48c4a.yaml b/releasenotes/notes/add-nova-compute-ironic-failover-doc-a0c4f45b1fb48c4a.yaml new file mode 100644 index 000000000..c5b52984f --- /dev/null +++ b/releasenotes/notes/add-nova-compute-ironic-failover-doc-a0c4f45b1fb48c4a.yaml @@ -0,0 +1,12 @@ +--- +fixes: + - | + Adds basic support and a document explaining how to migrate to a single + nova-compute-ironic instance, and how to re-deploy the instance to another + machine in the event of failure. See the operations / nova-compute-ironic + doc for further details. +upgrade: + - | + Ensure that your deployment has only one nova-compute-ironic service running + per conductor group. See the operations / nova-compute-ironic doc for further + details. From ce924149b618cb05fea0b06c97c23cda9f2a9a2c Mon Sep 17 00:00:00 2001 From: Jake Hutchinson Date: Fri, 8 Dec 2023 16:46:50 +0000 Subject: [PATCH 070/128] Post service deploy hook for OpenStack Capacity --- doc/source/configuration/monitoring.rst | 51 +++++++------------ .../ansible/deploy-os-capacity-exporter.yml | 22 +++++++- .../templates/os_capacity-clouds.yml.j2 | 8 +-- .../post.d/deploy-os-capacity-exporter.yml | 1 + etc/kayobe/stackhpc-monitoring.yml | 8 +-- ...capacity-deploy-hook-b52e87c0819df6fd.yaml | 9 ++++ 6 files changed, 54 insertions(+), 45 deletions(-) create mode 120000 etc/kayobe/hooks/overcloud-service-deploy/post.d/deploy-os-capacity-exporter.yml create mode 100644 releasenotes/notes/os-capacity-deploy-hook-b52e87c0819df6fd.yaml diff --git a/doc/source/configuration/monitoring.rst b/doc/source/configuration/monitoring.rst index 819da9769..f23c7a915 100644 --- a/doc/source/configuration/monitoring.rst +++ b/doc/source/configuration/monitoring.rst @@ -141,36 +141,26 @@ OpenStack Capacity ================== OpenStack Capacity allows you to see how much space you have available -in your cloud. StackHPC Kayobe Config includes a playbook for manual -deployment, and it's necessary that some variables are set before -running this playbook. +in your cloud. StackHPC Kayobe Config will deploy OpenStack Capacity +by default on a service deploy, this can be disabled by setting +``stackhpc_enable_os_capacity`` to false. -To successfully deploy OpenStack Capacity, you are required to specify -the OpenStack application credentials in ``kayobe/secrets.yml`` as: +OpenStack Capacity is deployed automatically using a service deploy hook +with the generated kolla-ansible admin credentials, you can override these +by setting the authentication url, username, password, project name and +project domain name in ``stackhpc-monitoring.yml``: .. code-block:: yaml - secrets_os_capacity_credential_id: - secrets_os_capacity_credential_secret: + stackhpc_os_capacity_auth_url: + stackhpc_os_capacity_username: + stackhpc_os_capacity_password: + stackhpc_os_capacity_project_name: + stackhpc_os_capacity_domain_name: + stackhpc_os_capacity_openstack_region_name: -The Keystone authentication URL and OpenStack region can be changed -from their defaults in ``stackhpc-monitoring.yml`` should you need to -set a different OpenStack region for your cloud. The authentication -URL is set to use ``kolla_internal_fqdn`` by default: - -.. code-block:: yaml - - stackhpc_os_capacity_auth_url: - stackhpc_os_capacity_openstack_region_name: - -Additionally, you are required to enable a conditional flag to allow -HAProxy and Prometheus configuration to be templated during deployment. - -.. code-block:: yaml - - stackhpc_enable_os_capacity: true - -If you are deploying in a cloud with internal TLS, you may be required +Additionally, you should ensure these credentials have the correct permissions +for the exporter. If you are deploying in a cloud with internal TLS, you may be required to disable certificate verification for the OpenStack Capacity exporter if your certificate is not signed by a trusted CA. @@ -178,21 +168,14 @@ if your certificate is not signed by a trusted CA. stackhpc_os_capacity_openstack_verify: false -After defining your credentials, you may deploy OpenStack Capacity -using the ``ansible/deploy-os-capacity-exporter.yml`` Ansible playbook +If you've modified your credentials, you will need to re-deploy OpenStack Capacity +using the ``deploy-os-capacity-exporter.yml`` Ansible playbook via Kayobe. .. code-block:: console kayobe playbook run $KAYOBE_CONFIG_PATH/ansible/deploy-os-capacity-exporter.yml -It is required that you re-configure the Prometheus, Grafana and HAProxy -services following deployment, to do this run the following Kayobe command. - -.. code-block:: console - - kayobe overcloud service reconfigure -kt grafana,prometheus,loadbalancer - If you notice ``HaproxyServerDown`` or ``HaproxyBackendDown`` prometheus alerts after deployment it's likely the os_exporter secrets have not been set correctly, double check you have entered the correct authentication diff --git a/etc/kayobe/ansible/deploy-os-capacity-exporter.yml b/etc/kayobe/ansible/deploy-os-capacity-exporter.yml index 8cff6a89e..978c13e62 100644 --- a/etc/kayobe/ansible/deploy-os-capacity-exporter.yml +++ b/etc/kayobe/ansible/deploy-os-capacity-exporter.yml @@ -17,14 +17,33 @@ ansible.builtin.file: path: /opt/kayobe/os-capacity/ state: directory + when: stackhpc_enable_os_capacity + + - name: Read admin-openrc credential file + ansible.builtin.command: + cmd: "cat {{ lookup('ansible.builtin.env', 'KOLLA_CONFIG_PATH') }}/admin-openrc.sh" + delegate_to: localhost + register: credential + when: stackhpc_enable_os_capacity + + - name: Set facts for admin credentials + ansible.builtin.set_fact: + stackhpc_os_capacity_auth_url: "{{ credential.stdout_lines | select('match', '.*OS_AUTH_URL*.') | first | split('=') | last | replace(\"'\",'') }}" + stackhpc_os_capacity_project_name: "{{ credential.stdout_lines | select('match', '.*OS_PROJECT_NAME*.') | first | split('=') | last | replace(\"'\",'') }}" + stackhpc_os_capacity_domain_name: "{{ credential.stdout_lines | select('match', '.*OS_PROJECT_DOMAIN_NAME*.') | first | split('=') | last | replace(\"'\",'') }}" + stackhpc_os_capacity_openstack_region_name: "{{ credential.stdout_lines | select('match', '.*OS_REGION_NAME*.') | first | split('=') | last | replace(\"'\",'') }}" + stackhpc_os_capacity_username: "{{ credential.stdout_lines | select('match', '.*OS_USERNAME*.') | first | split('=') | last | replace(\"'\",'') }}" + stackhpc_os_capacity_password: "{{ credential.stdout_lines | select('match', '.*OS_PASSWORD*.') | first | split('=') | last | replace(\"'\",'') }}" + when: stackhpc_enable_os_capacity - name: Template clouds.yml ansible.builtin.template: src: templates/os_capacity-clouds.yml.j2 dest: /opt/kayobe/os-capacity/clouds.yaml + when: stackhpc_enable_os_capacity - name: Ensure os_capacity container is running - docker_container: + community.docker.docker_container: name: os_capacity image: ghcr.io/stackhpc/os-capacity:master env: @@ -37,3 +56,4 @@ network_mode: host restart_policy: unless-stopped become: true + when: stackhpc_enable_os_capacity diff --git a/etc/kayobe/ansible/templates/os_capacity-clouds.yml.j2 b/etc/kayobe/ansible/templates/os_capacity-clouds.yml.j2 index a821d6dcb..ef3c8d7a5 100644 --- a/etc/kayobe/ansible/templates/os_capacity-clouds.yml.j2 +++ b/etc/kayobe/ansible/templates/os_capacity-clouds.yml.j2 @@ -2,12 +2,14 @@ clouds: openstack: auth: auth_url: "{{ stackhpc_os_capacity_auth_url }}" - application_credential_id: "{{ secrets_os_capacity_credential_id }}" - application_credential_secret: "{{ secrets_os_capacity_credential_secret }}" + project_name: "{{ stackhpc_os_capacity_project_name }}" + domain_name: "{{ stackhpc_os_capacity_domain_name }}" + username: "{{ stackhpc_os_capacity_username }}" + password: "{{ stackhpc_os_capacity_password }}" region_name: "{{ stackhpc_os_capacity_openstack_region_name }}" interface: "internal" identity_api_version: 3 - auth_type: "v3applicationcredential" + auth_type: "password" {% if not stackhpc_os_capacity_openstack_verify | bool %} verify: False {% endif %} diff --git a/etc/kayobe/hooks/overcloud-service-deploy/post.d/deploy-os-capacity-exporter.yml b/etc/kayobe/hooks/overcloud-service-deploy/post.d/deploy-os-capacity-exporter.yml new file mode 120000 index 000000000..0cc70aace --- /dev/null +++ b/etc/kayobe/hooks/overcloud-service-deploy/post.d/deploy-os-capacity-exporter.yml @@ -0,0 +1 @@ +../../../ansible/deploy-os-capacity-exporter.yml \ No newline at end of file diff --git a/etc/kayobe/stackhpc-monitoring.yml b/etc/kayobe/stackhpc-monitoring.yml index 13bf6ba0f..f08e552c3 100644 --- a/etc/kayobe/stackhpc-monitoring.yml +++ b/etc/kayobe/stackhpc-monitoring.yml @@ -14,13 +14,7 @@ alertmanager_low_memory_threshold_gib: 5 # Whether the OpenStack Capacity exporter is enabled. # Enabling this flag will result in HAProxy configuration and Prometheus scrape # targets being templated during deployment. -stackhpc_enable_os_capacity: false - -# Keystone authentication URL for OpenStack Capacity -stackhpc_os_capacity_auth_url: "http{% if kolla_enable_tls_internal | bool %}s{% endif %}://{{ kolla_internal_fqdn }}:5000" - -# OpenStack region for OpenStack Capacity -stackhpc_os_capacity_openstack_region_name: "RegionOne" +stackhpc_enable_os_capacity: true # Whether TLS certificate verification is enabled for the OpenStack Capacity # exporter during Keystone authentication. diff --git a/releasenotes/notes/os-capacity-deploy-hook-b52e87c0819df6fd.yaml b/releasenotes/notes/os-capacity-deploy-hook-b52e87c0819df6fd.yaml new file mode 100644 index 000000000..547939199 --- /dev/null +++ b/releasenotes/notes/os-capacity-deploy-hook-b52e87c0819df6fd.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Automatic deployment for OpenStack Capacity via a Kayobe service + deploy hook using kolla admin credentials. +upgrade: + - | + OpenStack Capacity no longer uses application credentials. Please + delete any previously generated application credentials. \ No newline at end of file From a8cefd05ee2a0e30d752bad5e55391df0a39cdd0 Mon Sep 17 00:00:00 2001 From: Doug Szumski Date: Fri, 8 Mar 2024 11:05:04 +0000 Subject: [PATCH 071/128] squash: Address comments from Alex --- doc/source/operations/nova-compute-ironic.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/doc/source/operations/nova-compute-ironic.rst b/doc/source/operations/nova-compute-ironic.rst index e139fa050..cbed4b753 100644 --- a/doc/source/operations/nova-compute-ironic.rst +++ b/doc/source/operations/nova-compute-ironic.rst @@ -61,12 +61,13 @@ following section. Moving from multiple Nova Compute Instances to a single instance ---------------------------------------------------------------- -1. Decide where the single instance should run. Typically, this will be - one of the three control plane hosts. Once you have chosen, set - the following variable in ``etc/kayobe/nova.yml``. Here we have - picked ``controller1``. +1. Decide where the single instance should run. This should normally be + one of the three OpenStack control plane hosts. For convention, pick + the first one, unless you can think of a good reason not to. Once you + have chosen, set the following variable in ``etc/kayobe/nova.yml``. + Here we have picked ``controller1``. - .. code-block:: console + .. code-block:: yaml kolla_nova_compute_ironic_host: controller1 @@ -193,7 +194,7 @@ same name as the one it replaces. For example, if the original instance resides on ``controller1``, then set the following in ``etc/kayobe/nova.yml``: -.. code-block:: console +.. code-block:: yaml kolla_nova_compute_ironic_static_host_name: controller1-ironic From 417d7acfbe20e2e960d191c978a59870b7d88cc5 Mon Sep 17 00:00:00 2001 From: Doug Szumski Date: Fri, 8 Mar 2024 13:18:13 +0000 Subject: [PATCH 072/128] Expand notes on re-deploying --- doc/source/operations/nova-compute-ironic.rst | 91 ++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/doc/source/operations/nova-compute-ironic.rst b/doc/source/operations/nova-compute-ironic.rst index cbed4b753..908247678 100644 --- a/doc/source/operations/nova-compute-ironic.rst +++ b/doc/source/operations/nova-compute-ironic.rst @@ -41,7 +41,11 @@ Ironic nodes can be managed. In many environments the loss of the Ironic API for short periods is acceptable, providing that it can be easily resurrected. The purpose of this document is to faciliate that. -TODO: Add caveats about new sharding mode (not covered here). +.. note:: + + The new sharding mode is not covered here and it is assumed that you are + not using it. See [1] for further information. This will be updated in + the future. Optimal configuration of Nova Compute Ironic ============================================ @@ -208,5 +212,90 @@ See [1] for further details. TODO: Investigate KA bug with assumption about host field. +Re-deploying Nova Compute Ironic +-------------------------------- + +The decision to re-deploy Nova Compute Ironic to another host should only be +taken if there is a strong reason to do so. The objective is to minimise +the chance of the old instance starting up alongside the new one. If the +original host has been re-imaged, or physically replaced there is no risk. +However, if the original host has been taken down for non-destructive +maintenance, it is better to avoid re-deploying the service if the end users +can tolerate the wait. If you are forced to re-deploy the service, knowing +that the original instance may start when the host comes back online, you +must plan accordingly. For example, by booting the original host in maintenance +mode and removing the old service before it can start, or by stopping the +new instance before the original one comes back up, and then reverting the +config to move it to the new host. + +There are essentially two scenarios for re-deploying Nova Compute Ironic. +These are described in the following sub-sections: + +Current host is accessible +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Adjust the ``kolla_nova_compute_ironic_host`` variable to point to the +new host, eg. + +.. code-block:: diff + + +kolla_nova_compute_ironic_host: controller2 + -kolla_nova_compute_ironic_host: controller1 + +Remove the old container: + +.. code-block:: console + + $ ssh controller1 sudo docker rm -f nova_compute_ironic + +Deploy the new service: + +.. code-block:: console + + $ kayobe overcloud service deploy -kl controller2 -l controller2 -kt nova + +Verify that the new service appears as 'up' and 'enabled': + +.. code-block:: console + + $ openstack compute service list + +Current host is not accessible +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In this case you will need to remove the inaccessible host from the inventory. +For example, in ``etc/kayobe/inventory/hosts``, remove ``controller1`` from +the ``controllers`` group. + +Adjust the ``kolla_nova_compute_ironic_host`` variable to point to the +new host, eg. + +.. code-block:: diff + + +kolla_nova_compute_ironic_host: controller2 + -kolla_nova_compute_ironic_host: controller1 + +Deploy the new service: + +.. code-block:: console + + $ kayobe overcloud service reconfigure -kl controller2 -l controller2 -kt nova + +Verify that the new service appears as 'up' and 'enabled': + +.. code-block:: console + + $ openstack compute service list + +.. note:: + + It is important to stop the original service from starting up again. It is + up to you to prevent this. + +.. note:: + + Once merged, the work on 'Kayobe reliability' may allow this step to run + without modifying the inventory to remove the broken host. + [1] https://specs.openstack.org/openstack/nova-specs/specs/2024.1/approved/ironic-shards.html#migrate-from-peer-list-to-shard-key [2] https://www.cloudfest.com/world-server-throwing-championship From fcfff10a8605bdedd8980a7dc7add7ad0be36c3f Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Fri, 8 Mar 2024 14:16:45 +0000 Subject: [PATCH 073/128] Add Trivy image scanning (#436) Trivy scanning on container image build --------- Co-authored-by: k-s-dean Co-authored-by: Matt Anson Co-authored-by: Alex-Welsh --- .../stackhpc-container-image-build.yml | 131 ++++++++++++++---- etc/kayobe/ansible/docker-registry-login.yml | 11 ++ ...ainer-image-scanning-e5adf2c6b540b502.yaml | 6 + tools/scan-images.sh | 79 +++++++++++ 4 files changed, 201 insertions(+), 26 deletions(-) create mode 100644 etc/kayobe/ansible/docker-registry-login.yml create mode 100644 releasenotes/notes/container-image-scanning-e5adf2c6b540b502.yaml create mode 100755 tools/scan-images.sh diff --git a/.github/workflows/stackhpc-container-image-build.yml b/.github/workflows/stackhpc-container-image-build.yml index b8afea93e..ad3097d0a 100644 --- a/.github/workflows/stackhpc-container-image-build.yml +++ b/.github/workflows/stackhpc-container-image-build.yml @@ -38,6 +38,12 @@ on: type: boolean required: false default: true + push-dirty: + description: Push scanned images that have vulnerabilities? + type: boolean + required: false + # NOTE(Alex-Welsh): This default should be flipped once we resolve existing failures + default: true env: ANSIBLE_FORCE_COLOR: True @@ -109,7 +115,15 @@ jobs: - name: Install package dependencies run: | sudo apt update - sudo apt install -y build-essential git unzip nodejs python3-wheel python3-pip python3-venv + sudo apt install -y build-essential git unzip nodejs python3-wheel python3-pip python3-venv curl jq wget + + - name: Install gh + run: | + sudo mkdir -p -m 755 /etc/apt/keyrings && wget -qO- https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null + sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null + sudo apt update + sudo apt install gh -y - name: Checkout uses: actions/checkout@v4 @@ -127,6 +141,10 @@ jobs: run: | docker ps + - name: Install Trivy + run: | + curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo sh -s -- -b /usr/local/bin v0.49.0 + - name: Install Kayobe run: | mkdir -p venvs && @@ -162,65 +180,124 @@ jobs: env: KAYOBE_VAULT_PASSWORD: ${{ secrets.KAYOBE_VAULT_PASSWORD }} - - name: Build and push kolla overcloud images + - name: Create build logs output directory + run: mkdir image-build-logs + + - name: Build kolla overcloud images + id: build_overcloud_images + continue-on-error: true run: | - args="${{ github.event.inputs.regexes }}" + args="${{ inputs.regexes }}" args="$args -e kolla_base_distro=${{ matrix.distro }}" args="$args -e kolla_tag=${{ needs.generate-tag.outputs.kolla_tag }}" args="$args -e stackhpc_repo_mirror_auth_proxy_enabled=true" - if ${{ inputs.push }} == 'true'; then - args="$args --push" - fi source venvs/kayobe/bin/activate && source src/kayobe-config/kayobe-env --environment ci-builder && kayobe overcloud container image build $args env: KAYOBE_VAULT_PASSWORD: ${{ secrets.KAYOBE_VAULT_PASSWORD }} - if: github.event.inputs.overcloud == 'true' + if: inputs.overcloud + + - name: Copy overcloud container image build logs to output directory + run: sudo mv /var/log/kolla-build.log image-build-logs/kolla-build-overcloud.log + if: inputs.overcloud - - name: Build and push kolla seed images + - name: Build kolla seed images + id: build_seed_images + continue-on-error: true run: | args="-e kolla_base_distro=${{ matrix.distro }}" args="$args -e kolla_tag=${{ needs.generate-tag.outputs.kolla_tag }}" args="$args -e stackhpc_repo_mirror_auth_proxy_enabled=true" - if ${{ inputs.push }} == 'true'; then - args="$args --push" - fi source venvs/kayobe/bin/activate && source src/kayobe-config/kayobe-env --environment ci-builder && kayobe seed container image build $args env: KAYOBE_VAULT_PASSWORD: ${{ secrets.KAYOBE_VAULT_PASSWORD }} - if: github.event.inputs.seed == 'true' + if: inputs.seed + + - name: Copy seed container image build logs to output directory + run: sudo mv /var/log/kolla-build.log image-build-logs/kolla-build-seed.log + if: inputs.seed - name: Get built container images - run: | - docker image ls --filter "reference=ark.stackhpc.com/stackhpc-dev/${{ matrix.distro }}-*:${{ needs.generate-tag.outputs.kolla_tag }}" > ${{ matrix.distro }}-container-images + run: docker image ls --filter "reference=ark.stackhpc.com/stackhpc-dev/${{ matrix.distro }}-*:${{ needs.generate-tag.outputs.kolla_tag }}" > ${{ matrix.distro }}-container-images - name: Fail if no images have been built run: if [ $(wc -l < ${{ matrix.distro }}-container-images) -le 1 ]; then exit 1; fi - - name: Upload container images artifact + - name: Scan built container images + run: src/kayobe-config/tools/scan-images.sh ${{ matrix.distro }} ${{ needs.generate-tag.outputs.kolla_tag }} + + - name: Move image scan logs to output artifact + run: mv image-scan-output image-build-logs/image-scan-output + + - name: Fail if no images have passed scanning + run: if [ $(wc -l < image-build-logs/image-scan-output/clean-images.txt) -le 0 ]; then exit 1; fi + if: ${{ !inputs.push-dirty }} + + - name: Copy clean images to push-attempt-images list + run: cp image-build-logs/image-scan-output/clean-images.txt image-build-logs/push-attempt-images.txt + if: inputs.push + + - name: Append dirty images to push list + run: | + cat image-build-logs/image-scan-output/dirty-images.txt >> image-build-logs/push-attempt-images.txt + if: ${{ inputs.push && inputs.push-dirty }} + + - name: Push images + run: | + touch image-build-logs/push-failed-images.txt + source venvs/kayobe/bin/activate && + source src/kayobe-config/kayobe-env --environment ci-builder && + kayobe playbook run ${KAYOBE_CONFIG_PATH}/ansible/docker-registry-login.yml && + + while read -r image; do + # Retries! + for i in {1..5}; do + if docker push $image; then + echo "Pushed $image" + break + elif $i == 5; then + echo "Failed to push $image" + echo $image >> image-build-logs/push-failed-images.txt + else + echo "Failed on retry $i" + sleep 5 + fi; + done + done < image-build-logs/push-attempt-images.txt + shell: bash + env: + KAYOBE_VAULT_PASSWORD: ${{ secrets.KAYOBE_VAULT_PASSWORD }} + if: inputs.push + + - name: Upload output artifact uses: actions/upload-artifact@v4 with: - name: ${{ matrix.distro }} container images - path: ${{ matrix.distro }}-container-images + name: ${{ matrix.distro }}-logs + path: image-build-logs retention-days: 7 + if: ${{ !cancelled() }} + + - name: Fail when images failed to build + run: echo "An image build failed. Check the workflow artifact for build logs" && exit 1 + if: ${{ steps.build_overcloud_images.outcome == 'failure' || steps.build_seed_images.outcome == 'failure' }} + + - name: Fail when images failed to push + run: if [ $(wc -l < image-build-logs/push-failed-images.txt) -gt 0 ]; then cat image-build-logs/push-failed-images.txt && exit 1; fi + if: ${{ !cancelled() }} + + - name: Fail when images failed scanning + run: if [ $(wc -l < image-build-logs/dirty-images.txt) -gt 0 ]; then cat image-build-logs/dirty-images.txt && exit 1; fi + if: ${{ !inputs.push-dirty && !cancelled() }} - sync-container-repositories: - name: Trigger container image repository sync - needs: - - container-image-build - if: github.repository == 'stackhpc/stackhpc-kayobe-config' && inputs.push - runs-on: ubuntu-latest - permissions: {} - steps: # NOTE(mgoddard): Trigger another CI workflow in the # stackhpc-release-train repository. - name: Trigger container image repository sync run: | filter='${{ inputs.regexes }}' - if [[ -n $filter ]] && [[ ${{ github.event.inputs.seed }} == 'true' ]]; then + if [[ -n $filter ]] && [[ ${{ inputs.seed }} == 'true' ]]; then filter="$filter bifrost" fi gh workflow run \ @@ -231,7 +308,9 @@ jobs: -f sync-new-images=false env: GITHUB_TOKEN: ${{ secrets.STACKHPC_RELEASE_TRAIN_TOKEN }} + if: ${{ github.repository == 'stackhpc/stackhpc-kayobe-config' && inputs.push && !cancelled() }} - name: Display link to container image repository sync workflows run: | echo "::notice Container image repository sync workflows: https://github.com/stackhpc/stackhpc-release-train/actions/workflows/container-sync.yml" + if: ${{ github.repository == 'stackhpc/stackhpc-kayobe-config' && inputs.push && !cancelled() }} diff --git a/etc/kayobe/ansible/docker-registry-login.yml b/etc/kayobe/ansible/docker-registry-login.yml new file mode 100644 index 000000000..39ad03600 --- /dev/null +++ b/etc/kayobe/ansible/docker-registry-login.yml @@ -0,0 +1,11 @@ +--- +- name: Login to docker registry + gather_facts: false + hosts: container-image-builders + tasks: + - name: Login to docker registry + docker_login: + registry_url: "{{ kolla_docker_registry or omit }}" + username: "{{ kolla_docker_registry_username }}" + password: "{{ kolla_docker_registry_password }}" + reauthorize: yes diff --git a/releasenotes/notes/container-image-scanning-e5adf2c6b540b502.yaml b/releasenotes/notes/container-image-scanning-e5adf2c6b540b502.yaml new file mode 100644 index 000000000..67a99f9c2 --- /dev/null +++ b/releasenotes/notes/container-image-scanning-e5adf2c6b540b502.yaml @@ -0,0 +1,6 @@ +--- +security: + - | + Kolla container images created using the + ``stackhpc-container-image-build.yml`` workflow are now automatically + scanned for vulnerablilities. diff --git a/tools/scan-images.sh b/tools/scan-images.sh new file mode 100755 index 000000000..50a04185a --- /dev/null +++ b/tools/scan-images.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +set -eo pipefail + +# Check correct usage +if [[ ! $2 ]]; then + echo "Usage: scan-images.sh " + exit 2 +fi + +set -u + +# Check that trivy is installed +if ! trivy --version; then + echo 'Please install trivy: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v0.49.1' +fi + +# Clear any previous outputs +rm -rf image-scan-output + +# Make a fresh output directory +mkdir -p image-scan-output + +# Get built container images +docker image ls --filter "reference=ark.stackhpc.com/stackhpc-dev/$1-*:$2" > $1-scanned-container-images.txt + +# Make a file of imagename:tag +images=$(grep --invert-match --no-filename ^REPOSITORY $1-scanned-container-images.txt | sed 's/ \+/:/g' | cut -f 1,2 -d:) + +# Ensure output files exist +touch image-scan-output/clean-images.txt image-scan-output/dirty-images.txt + +# If Trivy detects no vulnerabilities, add the image name to clean-images.txt. +# If there are vulnerabilities detected, add it to dirty-images.txt and +# generate a csv summary +for image in $images; do + filename=$(basename $image | sed 's/:/\./g') + if $(trivy image \ + --quiet \ + --exit-code 1 \ + --scanners vuln \ + --format json \ + --severity HIGH,CRITICAL \ + --output image-scan-output/${filename}.json \ + --ignore-unfixed \ + $image); then + # Clean up the output file for any images with no vulnerabilities + rm -f image-scan-output/${filename}.json + + # Add the image to the clean list + echo "${image}" >> image-scan-output/clean-images.txt + else + # Add the image to the dirty list + echo "${image}" >> image-scan-output/dirty-images.txt + + # Write a header for the summary CSV + echo '"PkgName","PkgPath","PkgID","VulnerabilityID","FixedVersion","PrimaryURL","Severity"' > image-scan-output/${filename}.summary.csv + + # Write the summary CSV data + jq -r '.Results[] + | select(.Vulnerabilities) + | .Vulnerabilities + # Ignore packages with "kernel" in the PkgName + | map(select(.PkgName | test("kernel") | not )) + | group_by(.VulnerabilityID) + | map( + [ + (map(.PkgName) | unique | join(";")), + (map(.PkgPath | select( . != null )) | join(";")), + .[0].PkgID, + .[0].VulnerabilityID, + .[0].FixedVersion, + .[0].PrimaryURL, + .[0].Severity + ] + ) + | .[] + | @csv' image-scan-output/${filename}.json >> image-scan-output/${filename}.summary.csv + fi +done From 2ec32ceaf155d1876ed15e7849386d44c373683b Mon Sep 17 00:00:00 2001 From: scrungus Date: Fri, 8 Mar 2024 15:39:16 +0000 Subject: [PATCH 074/128] bump magnum-capi-helm version --- etc/kayobe/kolla.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/kayobe/kolla.yml b/etc/kayobe/kolla.yml index 8844a3cbd..ebf8b0929 100644 --- a/etc/kayobe/kolla.yml +++ b/etc/kayobe/kolla.yml @@ -415,7 +415,7 @@ kolla_build_blocks: magnum_base_footer: | RUN curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | head -n -1 | bash {% raw %} - {% set magnum_capi_packages = ['git+https://github.com/stackhpc/magnum-capi-helm.git@v0.10.0'] %} + {% set magnum_capi_packages = ['git+https://github.com/stackhpc/magnum-capi-helm.git@v0.11.0'] %} RUN {{ macros.install_pip(magnum_capi_packages | customizable("pip_packages")) }} {% endraw %} # Dict mapping image customization variable names to their values. From ee1aa833a13977a159c5a6a12a062ee38868ad76 Mon Sep 17 00:00:00 2001 From: Doug Szumski Date: Fri, 8 Mar 2024 15:41:04 +0000 Subject: [PATCH 075/128] Add note about upstream bug --- doc/source/operations/nova-compute-ironic.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/source/operations/nova-compute-ironic.rst b/doc/source/operations/nova-compute-ironic.rst index 908247678..6cbe00550 100644 --- a/doc/source/operations/nova-compute-ironic.rst +++ b/doc/source/operations/nova-compute-ironic.rst @@ -210,7 +210,13 @@ It is also possible to use an arbitrary ``host`` name, but you will need to edit the database again. That is an optional exercise left for the reader. See [1] for further details. -TODO: Investigate KA bug with assumption about host field. +.. note:: + + There is a bug when overriding the host name in Kolla Ansible, where it + is currently assumed that it will be set to the actual hostname + an + -ironic postfix. The service will come up correctly, but Kolla Ansible + will not detect it. See here: + https://bugs.launchpad.net/kolla-ansible/+bug/2056571 Re-deploying Nova Compute Ironic -------------------------------- From 8d7077f286dfe37b540e1f9cd454677f44b30ac5 Mon Sep 17 00:00:00 2001 From: scrungus Date: Fri, 8 Mar 2024 15:43:41 +0000 Subject: [PATCH 076/128] reno --- .../notes/bump-magnum-capi-helm-6723d89456e6a590.yaml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 releasenotes/notes/bump-magnum-capi-helm-6723d89456e6a590.yaml diff --git a/releasenotes/notes/bump-magnum-capi-helm-6723d89456e6a590.yaml b/releasenotes/notes/bump-magnum-capi-helm-6723d89456e6a590.yaml new file mode 100644 index 000000000..864edf44b --- /dev/null +++ b/releasenotes/notes/bump-magnum-capi-helm-6723d89456e6a590.yaml @@ -0,0 +1,4 @@ +--- +upgrade: + - | + Updates Magnum CAPI Helm driver version to v0.11.0 From 2ae28e016a8b2c3614feb6728d3795b95ae1481e Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Fri, 8 Mar 2024 21:31:35 +0100 Subject: [PATCH 077/128] Fix Ceph "Objects in the Cluster" dashboard panel The `ceph_cluster_total_objects` metric was removed several releases ago [1]. Use `ceph_pool_objects` which provides per-pool metrics. [1] https://github.com/ceph/ceph-ansible/issues/6032 --- .../grafana/dashboards/ceph/ceph_overview.json | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/etc/kayobe/kolla/config/grafana/dashboards/ceph/ceph_overview.json b/etc/kayobe/kolla/config/grafana/dashboards/ceph/ceph_overview.json index e041d8ff0..e5258168a 100644 --- a/etc/kayobe/kolla/config/grafana/dashboards/ceph/ceph_overview.json +++ b/etc/kayobe/kolla/config/grafana/dashboards/ceph/ceph_overview.json @@ -1924,23 +1924,25 @@ } ], "spaceLength": 10, - "stack": true, + "stack": false, "steppedLine": false, "targets": [ { - "expr": "ceph_cluster_total_objects", + "datasource": { + "uid": "$datasource" + }, + "expr": "ceph_pool_objects * on(pool_id) group_left(instance,name) ceph_pool_metadata", "format": "time_series", "interval": "$interval", "intervalFactor": 1, - "legendFormat": "Total", + "legendFormat": "{{name}}", + "range": true, "refId": "A", "step": 300 } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "Objects in the Cluster", "tooltip": { "msResolution": false, From 1e33f3da156ce7e1c2e586abccc8e4fe7555f235 Mon Sep 17 00:00:00 2001 From: Alex-Welsh Date: Mon, 11 Mar 2024 09:11:06 +0000 Subject: [PATCH 078/128] Fix tempest doc long line --- doc/source/operations/tempest.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/source/operations/tempest.rst b/doc/source/operations/tempest.rst index 82135adf9..b3fa2b5a8 100644 --- a/doc/source/operations/tempest.rst +++ b/doc/source/operations/tempest.rst @@ -277,7 +277,10 @@ command from the base of the ``kayobe-config`` directory: .. code-block:: bash - sudo -E docker run --detach -it --rm --network host -v $(pwd):/stack/kayobe-automation-env/src/kayobe-config -v $(pwd)/tempest-artifacts:/stack/tempest-artifacts -e KAYOBE_ENVIRONMENT -e KAYOBE_VAULT_PASSWORD -e KAYOBE_AUTOMATION_SSH_PRIVATE_KEY kayobe:latest /stack/kayobe-automation-env/src/kayobe-config/.automation/pipeline/tempest.sh -e ansible_user=stack + sudo -E docker run --name kayobe-automation --detach -it --rm --network host \ + -v $(pwd):/stack/kayobe-automation-env/src/kayobe-config -v $(pwd)/tempest-artifacts:/stack/tempest-artifacts \ + -e KAYOBE_ENVIRONMENT -e KAYOBE_VAULT_PASSWORD -e KAYOBE_AUTOMATION_SSH_PRIVATE_KEY kayobe:latest \ + /stack/kayobe-automation-env/src/kayobe-config/.automation/pipeline/tempest.sh -e ansible_user=stack By default, ``no_log`` is set to stop credentials from leaking. This can be disabled by adding ``-e rally_no_sensitive_log=false`` to the end. From e989f4ffb1b38bc491ed605c88f9ae8aa6a6b40f Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Mon, 11 Mar 2024 09:44:36 +0000 Subject: [PATCH 079/128] CI: Support unmaintained branches in release determination --- .github/workflows/overcloud-host-image-build.yml | 2 +- .github/workflows/overcloud-host-image-promote.yml | 2 +- .github/workflows/overcloud-host-image-upload.yml | 2 +- .github/workflows/stackhpc-ci-cleanup.yml | 2 +- .github/workflows/stackhpc-container-image-build.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/overcloud-host-image-build.yml b/.github/workflows/overcloud-host-image-build.yml index cbccca23e..4c338cda3 100644 --- a/.github/workflows/overcloud-host-image-build.yml +++ b/.github/workflows/overcloud-host-image-build.yml @@ -50,7 +50,7 @@ jobs: id: openstack_release run: | BRANCH=$(awk -F'=' '/defaultbranch/ {print $2}' .gitreview) - echo "openstack_release=${BRANCH}" | sed "s|stable/||" >> $GITHUB_OUTPUT + echo "openstack_release=${BRANCH}" | sed -E "s,(stable|unmaintained)/,," >> $GITHUB_OUTPUT # Generate a tag to apply to all built overcloud host images. - name: Generate overcloud host image tag diff --git a/.github/workflows/overcloud-host-image-promote.yml b/.github/workflows/overcloud-host-image-promote.yml index 1bd777c8c..d5625f888 100644 --- a/.github/workflows/overcloud-host-image-promote.yml +++ b/.github/workflows/overcloud-host-image-promote.yml @@ -43,7 +43,7 @@ jobs: id: openstack_release run: | BRANCH=$(awk -F'=' '/defaultbranch/ {print $2}' .gitreview) - echo "openstack_release=${BRANCH}" | sed "s|stable/||" >> $GITHUB_OUTPUT + echo "openstack_release=${BRANCH}" | sed -E "s,(stable|unmaintained)/,," >> $GITHUB_OUTPUT working-directory: src/kayobe-config - name: Clone StackHPC Kayobe repository diff --git a/.github/workflows/overcloud-host-image-upload.yml b/.github/workflows/overcloud-host-image-upload.yml index 633f423b5..e95531564 100644 --- a/.github/workflows/overcloud-host-image-upload.yml +++ b/.github/workflows/overcloud-host-image-upload.yml @@ -59,7 +59,7 @@ jobs: id: openstack_release run: | BRANCH=$(awk -F'=' '/defaultbranch/ {print $2}' src/kayobe-config/.gitreview) - echo "openstack_release=${BRANCH}" | sed "s|stable/||" >> $GITHUB_OUTPUT + echo "openstack_release=${BRANCH}" | sed -E "s,(stable|unmaintained)/,," >> $GITHUB_OUTPUT - name: Clone StackHPC Kayobe repository uses: actions/checkout@v4 diff --git a/.github/workflows/stackhpc-ci-cleanup.yml b/.github/workflows/stackhpc-ci-cleanup.yml index d0da0c051..a769aa718 100644 --- a/.github/workflows/stackhpc-ci-cleanup.yml +++ b/.github/workflows/stackhpc-ci-cleanup.yml @@ -30,7 +30,7 @@ jobs: id: openstack_release run: | BRANCH=$(awk -F'=' '/defaultbranch/ {print $2}' src/kayobe-config/.gitreview) - echo "openstack_release=${BRANCH}" | sed "s|stable/||" >> $GITHUB_OUTPUT + echo "openstack_release=${BRANCH}" | sed -E "s,(stable|unmaintained)/,," >> $GITHUB_OUTPUT - name: Install OpenStack client run: | diff --git a/.github/workflows/stackhpc-container-image-build.yml b/.github/workflows/stackhpc-container-image-build.yml index b8afea93e..e41beb3c3 100644 --- a/.github/workflows/stackhpc-container-image-build.yml +++ b/.github/workflows/stackhpc-container-image-build.yml @@ -59,7 +59,7 @@ jobs: id: openstack_release run: | BRANCH=$(awk -F'=' '/defaultbranch/ {print $2}' .gitreview) - echo "openstack_release=${BRANCH}" | sed "s|stable/||" >> $GITHUB_OUTPUT + echo "openstack_release=${BRANCH}" | sed -E "s,(stable|unmaintained)/,," >> $GITHUB_OUTPUT # Generate a tag to apply to all built container images. # Without this, each kayobe * container image build command would use a different tag. From 842f205cbb8d044567d073312f74df458235c219 Mon Sep 17 00:00:00 2001 From: Scott Davidson <49713135+sd109@users.noreply.github.com> Date: Mon, 11 Mar 2024 16:27:28 +0000 Subject: [PATCH 080/128] Expand Magnum Cluster API docs (#972) * Expand Magnum Cluster API docs * Remove trailing whitespace * Address review feedback * Rename CAPI architecture diagram * Address more review comments * Tweak note formatting * Add link to relevant openstack-config docs * Fix lint failure * Fix typo Co-authored-by: Alex-Welsh <112560678+Alex-Welsh@users.noreply.github.com> --------- Co-authored-by: sd109 Co-authored-by: Alex-Welsh <112560678+Alex-Welsh@users.noreply.github.com> --- .../images/capi-architecture-diagram.png | Bin 0 -> 267742 bytes doc/source/configuration/magnum-capi.rst | 127 ++++++++++++++---- 2 files changed, 104 insertions(+), 23 deletions(-) create mode 100644 doc/source/_static/images/capi-architecture-diagram.png diff --git a/doc/source/_static/images/capi-architecture-diagram.png b/doc/source/_static/images/capi-architecture-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..259cb89390a22560ce0fcc3e9167d24a5a15d76f GIT binary patch literal 267742 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

si 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

Pd30D37 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


r|24LIGe6lD=5IrYIjgXvrm>dM-ze{ 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 literal 0 HcmV?d00001 diff --git a/doc/source/configuration/magnum-capi.rst b/doc/source/configuration/magnum-capi.rst index ab7dc8873..e93c0cd7a 100644 --- a/doc/source/configuration/magnum-capi.rst +++ b/doc/source/configuration/magnum-capi.rst @@ -1,45 +1,126 @@ ========================= Magnum Cluster API Driver ========================= -A new driver for magnum has been written. It is an alternative to heat (as heat gets phased out due to maintenance burden) that allows the definition of clusters as Kubernetes CRDs as opposed to heat templates. The two are compatible and can both be active on the same deployment, and the decision of which driver is used for a given template depends on certain parameters inferred from the template. For the new driver, these are `{'server_type' : 'vm', 'os' : 'ubuntu', 'coe': kubernetes'}`. -Drivers can be enabled and disabled via the `disabled_drivers` parameter of `[drivers]` under `magnum.conf`. -Prerequisites for deploying the CAPI driver in magnum: +A new driver for Magnum has been written which is an alternative to Heat (as Heat gets phased out due to maintenance burden) and instead uses the Kubernetes `Cluster API project `_ to manage the OpenStack infrastructure required by Magnum clusters. The idea behind the Cluster API (CAPI) project is that infrastructure is managed using Kubernetes-style declarative APIs, which in practice means a set of Custom Resource Definitions (CRDs) and Kubernetes `operators `_ to translate instances of those custom Kubernetes resources into the required OpenStack API resources. These same operators also handle resource reconciliation (i.e. when the Kubernetes custom resource is modified, the operator will make the required OpenStack API calls to reflect those changes). -Management Cluster -=================== -The CAPI driver relies on a management Kubernetes cluster, installed inside the cloud, to manage tenant Kubernetes clusters. -The easiest way to get one is by deploying `this `__ branch of azimuth-config, and look at the `capi-mgmt-example` environment. Refer to the `azimuth-config wiki `__ for detailed steps on how to deploy. +The new CAPI driver and the old Heat driver are compatible and can both be active on the same deployment, and the decision of which driver is used for a given template depends on certain parameters inferred from the Magnum cluster template. For the new driver, these parameters are ``{'server_type': 'vm', 'os': 'ubuntu', 'coe': kubernetes'}``. Drivers can be enabled and disabled using the ``disabled_drivers`` parameter in the ``[drivers]`` section of ``magnum.conf``. -Ensure that you have set `capi_cluster_apiserver_floating_ip: true`, as the management cluster will need an externally accessible IP. The external network this corresponds to is whatever you have set `azimuth_capi_operator_external_network_id` to. This network needs to be reachable from wherever the magnum container is running. +Deployment Prerequisites +======================== -It's preferable that most Day 2 ops be done via a `CD Pipeline `__. +The Cluster API architecture relies on a CAPI management cluster in order to run the aforementioned Kubernetes operators which interact directly with the OpenStack APIs. The two requirements for this management cluster are: -Kayobe Config -============== -Ensure that your kayobe-config branch is up to date on |current_release_git_branch_name|. +1. It must be capable of reaching the public OpenStack APIs. + +2. It must be reachable from the control plane nodes (either controllers or dedicated network hosts) on which the Magnum containers are running (so that the Magnum can reach the IP listed in the management cluster's ``kubeconfig`` file). + +For testing purposes, a simple `k3s `_ cluster would suffice. For production deployments, the recommended solution is to instead set up a separate HA management cluster in an isolated OpenStack project by leveraging the CAPI management cluster configuration used in `Azimuth `_. This approach will provide a resilient HA management cluster with a standard set of component versions that are regularly tested in Azimuth CI. +The general process for setting up this CAPI management cluster using Azimuth tooling is described here, but the `Azimuth operator documentation `_ should be consulted for additional information if required. + +The diagram below shows the general architecture of the CAPI management cluster provisioned using Azimuth tooling. It consists of a Seed VM (a terraform-provisioned OpenStack VM) running a small k3s cluster (which itself is actually a CAPI management cluster but only for the purpose of managing the HA cluster) as well as a HA management cluster made up of (by default) 3 control plane VMs and 3 worker VMs. This HA cluster runs the various Kubernetes components responsible for managing Magnum tenant clusters. + +.. image:: /_static/images/capi-architecture-diagram.png + :width: 100% + +The setup and configuration of a CAPI management cluster using Azimuth tooling follow a pattern that should be familiar to Kayobe operators. There is an 'upstream' `azimuth-config `_ repository which contains recommended defaults for various configuration options (equivalent to stackhpc-kayobe-config), and then each client site will maintain an independent copy of this repository which will contain site-specific configuration. Together, these upstream and site-specific configuration repositories can set or override Ansible variables for the `azimuth-ops `_ Ansible collection, which contains the playbooks required to deploy or update a CAPI management cluster (or a full Azimuth deployment). + +In order to deploy a CAPI management cluster for use with Magnum, first create a copy of the upstream Azimuth config repository in the client's GitHub/GitLab. To do so, follow the instructions found in the `initial repository setup `_ section of the Azimuth operator docs. The site-specific repository should then be encrypted following `these instructions `_ to avoid leaking any secrets (such as cloud credentials) that will be added to the configuration later on. + +Next, rather than copying the ``example`` environment as recommended in the Azimuth docs, instead copy the ``capi-mgmt-example`` environment and give it a suitable site-specific name: + +.. code-block:: bash + + cp -r ./environments/capi-mgmt-example ./environments/ -Copy the kubeconfig found at `kubeconfig-capi-mgmt-.yaml` to your kayobe environment (e.g. `/kolla/config/magnum/kubeconfig`. It is highly likely you'll want to add this file to ansible vault. +By default, both the seed VM name and the CAPI cluster VM names will be derived by prefixing the environment name with `capi-mgmt-` so naming the environment after the cloud (e.g. `sms-lab-prod`) is recommended. -Ensure that your magnum.conf has the following set: +Having created this concrete environment to hold site-specific configuration, next open ``environments//inventory/group-vars/all/variables.yml`` and, at a minimum, set the following options to the desired values for the target cloud: .. code-block:: yaml - [nova_client] - endpoint_type = publicURL + infra_external_network_id: + infra_flavor_id: + capi_cluster_control_plane_flavor: + capi_cluster_worker_flavor: + +The comments surrounding each option in the ``variables.yml`` provide some tips on choosing sensible values (e.g. resource requirements for each flavor). In most cases, other configuration options can be left blank since they will fall back to the upstream defaults; however, if the default configuration is not suitable, the roles in `ansible-collection-azimuth-ops `_ contain a range of config variables which can be overridden in ``variables.yml`` as required. In particular, the `infra role variables `_ are mostly relevant to the seed VM configuration, and the `capi_cluster role variables `_ are relevant for HA cluster config. + +.. note:: + + One important distinction between azimuth-config and stackhpc-kayobe-config is that the environments in azimuth-config are `layered`. This can be seen in the ``ansible.cfg`` file for each environment, which will contain a line of the form ``inventory = `` showing the inheritance chain for variables defined in each environment. See `these docs `_ for more details. + +In addition to setting the required infrastructure variables, Terraform must also be configured to use a remote state store (either GitLab or S3) for the seed VM state. To do so, follow the instructions found `here `_. + +The HA cluster also contains a deployment of `kube-prometheus-stack `_ for monitoring and alerting. To send the cluster alerts to Slack, the ``alertmanager_config_slack_webhook_url`` variable should be set in ``environments//inventory/group-vars/all/secrets.yml``. If the repository was encrypted correctly above, this file will automatically be encrypted before a git push. Run ``git-crypt status -e`` to verify that this file is included in the encrypted list before git-committing the webhook URL. + +The final step before beginning deployment of the CAPI management cluster is to provide some cloud credentials. It is recommended that the CAPI management cluster is deployed in an isolated OpenStack project. After creating the target project (preferably using `openstack-config `_), generate an application credential for the project using the Identity tab in Horizon and then download the corresponding ``clouds.yaml`` file and place it in ``environments//clouds.yaml``. + +To deploy the CAPI management cluster using this site-specific environment, run + +.. code-block:: bash + + # Activate the environment + ./bin/activate + # Install or update the local Ansible Python venv + ./bin/ensure-venv -This is used to generate the application credential config injected into the tenant Kubernetes clusters, such that it is usable from within an OpenStack project, so you can't use the "internal API" end point here. + # Install or update Ansible dependencies + ansible-galaxy install -f -r ./requirements.yml -Control Plane + # Run the provision playbook from the azimuth-ops collection + # NOTE: THIS COMMAND RUNS A DIFFERENT PLAYBOOK FROM + # THE STANDARD AZIMUTH DEPLOYMENT INSTRUCTIONS + ansible-playbook stackhpc.azimuth_ops.provision_capi_mgmt + +The general running order of the provisioning playbook is the following: + +- Ensure Terraform is installed locally + +- Use Terraform to provision the seed VM (and create any required internal networks, volumes etc.) + +- Install k3s on the seed (with all k3s data stored on the attached Cinder volume) + +- Install the required components on the k3s cluster to provision the HA cluster + +- Provision the HA cluster + +- Install the required components on the HA cluster to manage Magnum user clusters + +Once the seed VM has been provisioned, it can be accessed via SSH by running ``./bin/seed-ssh`` from the root of the azimuth-config repository. Within the seed VM, the k3s cluster and the HA cluster can both be accessed using the pre-installed ``kubectl`` and ``helm`` command line tools. Both of these tools will target the k3s cluster by default; however, the ``kubeconfig`` file for the HA cluster can be found in the seed's home directory (named e.g. ``kubeconfig-capi-mgmt-.yaml``). + +.. note:: + + The provision playbook is responsible for copying the HA ``kubeconfig`` to this location *after* the HA cluster is up and running. If you need to access the HA cluster while it is still deploying, the ``kubeconfig`` file can be found stored as a Kubernetes secret on the k3s cluster. + +It is possible to reconfigure or upgrade the management cluster after initial deployment by simply re-running the ``provision_capi_mgmt`` playbook. However, it's preferable that most Day 2 ops (i.e. reconfigures and upgrades) be done via a CD Pipeline. See `these Azimuth docs `_ for more information. + +Kayobe Config ============== -Ensure that the nodes (either controllers or dedicated network hosts) that you are running the magnum containers on have connectivity to the network on which your management cluster has a floating IP (so that the magnum containers can reach the IP listed in the kubeconfig). -Magnum Templates -================ +To configure the Magnum service with the Cluster API driver enabled, first ensure that your kayobe-config branch is up to date with |current_release_git_branch_name|. + +Next, copy the CAPI management cluster's kubeconfig file into your stackhpc-kayobe-config environment (e.g. ``/kolla/config/magnum/kubeconfig``). This file must be Ansible vault encrypted. + +The following config should also be set in your stackhpc-kayobe-config environment: + +.. code-block:: ini + :caption: magnum.conf + + [nova_client] + endpoint_type = publicURL + +.. code-block:: yaml + :caption: kolla/globals.yml + + magnum_cluster_api_driver_enabled: true -`azimuth-images `__ builds the required Ubuntu Kubernetes images, and `capi-helm-charts `__ CI runs conformance tests on each image built. +To apply the configuration, run ``kayobe overcloud service reconfigure -kt magnum``. -Magnum templates can be deployed using `openstack-config `__. Typically, you would create a fork `-config` of this repository, move the resources defined in `examples/capi-templates-images.yml` into `etc/openstack-config/openstack-config.yml`, and then follow the instructions in the readme to deploy these. +Magnum Cluster Templates +======================== +The clusters deployed by the Cluster API driver make use of the Ubuntu Kubernetes images built in the `azimuth-images `_ repository and then use `capi-helm-charts `_ to provide the Helm charts which define the clusters based on these images. Between them, these two repositories have CI jobs that regularly build and test images and Helm charts for the latest Kubernetes versions. It is therefore important to update the cluster templates on each cloud regularly to make use of these new releases. +Magnum templates should be defined within an existing client-specific `openstack-config `_ repository. See the openstack-config `README `_ for more details. From 1f0c49a7c888bfbb323c22f83ee825dfbf3b3414 Mon Sep 17 00:00:00 2001 From: Scott Davidson <49713135+sd109@users.noreply.github.com> Date: Tue, 12 Mar 2024 12:39:29 +0000 Subject: [PATCH 081/128] Fix broken link --- doc/source/configuration/magnum-capi.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/configuration/magnum-capi.rst b/doc/source/configuration/magnum-capi.rst index e93c0cd7a..302bf800c 100644 --- a/doc/source/configuration/magnum-capi.rst +++ b/doc/source/configuration/magnum-capi.rst @@ -50,7 +50,7 @@ The comments surrounding each option in the ``variables.yml`` provide some tips One important distinction between azimuth-config and stackhpc-kayobe-config is that the environments in azimuth-config are `layered`. This can be seen in the ``ansible.cfg`` file for each environment, which will contain a line of the form ``inventory = `` showing the inheritance chain for variables defined in each environment. See `these docs `_ for more details. -In addition to setting the required infrastructure variables, Terraform must also be configured to use a remote state store (either GitLab or S3) for the seed VM state. To do so, follow the instructions found `here `_. +In addition to setting the required infrastructure variables, Terraform must also be configured to use a remote state store (either GitLab or S3) for the seed VM state. To do so, follow the instructions found `here `_. The HA cluster also contains a deployment of `kube-prometheus-stack `_ for monitoring and alerting. To send the cluster alerts to Slack, the ``alertmanager_config_slack_webhook_url`` variable should be set in ``environments//inventory/group-vars/all/secrets.yml``. If the repository was encrypted correctly above, this file will automatically be encrypted before a git push. Run ``git-crypt status -e`` to verify that this file is included in the encrypted list before git-committing the webhook URL. From 5e0dc70ae9c3b8a57611faec19630131df303232 Mon Sep 17 00:00:00 2001 From: Matt Crees Date: Tue, 12 Mar 2024 14:33:36 +0000 Subject: [PATCH 082/128] Fix Jinja templating in Barbican Vault config The raw tags cause ``ssl_ca_crt_file`` to be templated without a newline on the end. This would give the following misconfiguration: ``` use_ssl = True ssl_ca_crt_file = approle_role_id = approle_secret_id = ``` --- doc/source/configuration/vault.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/source/configuration/vault.rst b/doc/source/configuration/vault.rst index 62bdaf24a..4cb39b61b 100644 --- a/doc/source/configuration/vault.rst +++ b/doc/source/configuration/vault.rst @@ -296,7 +296,9 @@ Configure Barbican [vault_plugin] vault_url = https://{{ kolla_internal_vip_address }}:8200 use_ssl = True - ssl_ca_crt_file = {% raw %}{{ openstack_cacert }}{% endraw %} + {% raw %} + ssl_ca_crt_file = {{ openstack_cacert }} + {% endraw %} approle_role_id = {{ secrets_barbican_approle_role_id }} approle_secret_id = {{ secrets_barbican_approle_secret_id }} kv_mountpoint = barbican From 6b54ecc3b3bbfbb731a0d507b965b10435c9e80c Mon Sep 17 00:00:00 2001 From: scrungus Date: Fri, 8 Mar 2024 15:39:16 +0000 Subject: [PATCH 083/128] bump magnum-capi-helm version --- etc/kayobe/kolla.yml | 2 +- .../notes/bump-magnum-capi-helm-7e4ad37d3d9eecce.yaml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bump-magnum-capi-helm-7e4ad37d3d9eecce.yaml diff --git a/etc/kayobe/kolla.yml b/etc/kayobe/kolla.yml index e64386c50..969d5fb84 100644 --- a/etc/kayobe/kolla.yml +++ b/etc/kayobe/kolla.yml @@ -326,7 +326,7 @@ kolla_build_blocks: magnum_base_footer: | RUN curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | head -n -1 | bash {% raw %} - {% set magnum_capi_packages = ['git+https://github.com/stackhpc/magnum-capi-helm.git@v0.10.0'] %} + {% set magnum_capi_packages = ['git+https://github.com/stackhpc/magnum-capi-helm.git@v0.11.0'] %} RUN {{ macros.install_pip(magnum_capi_packages | customizable("pip_packages")) }} {% endraw %} # Dict mapping image customization variable names to their values. diff --git a/releasenotes/notes/bump-magnum-capi-helm-7e4ad37d3d9eecce.yaml b/releasenotes/notes/bump-magnum-capi-helm-7e4ad37d3d9eecce.yaml new file mode 100644 index 000000000..883521087 --- /dev/null +++ b/releasenotes/notes/bump-magnum-capi-helm-7e4ad37d3d9eecce.yaml @@ -0,0 +1,4 @@ +--- +upgrade: + - | + Updates Magnum CAPI Helm driver version to v0.11.0 From 5d54d0b15dd7c59da10ea1c87ffa33d99bf8d681 Mon Sep 17 00:00:00 2001 From: sd109 Date: Tue, 12 Mar 2024 17:11:36 +0000 Subject: [PATCH 084/128] Bump container image tags --- etc/kayobe/kolla-image-tags.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/etc/kayobe/kolla-image-tags.yml b/etc/kayobe/kolla-image-tags.yml index 9358037cf..37a2f0b17 100644 --- a/etc/kayobe/kolla-image-tags.yml +++ b/etc/kayobe/kolla-image-tags.yml @@ -13,8 +13,8 @@ kolla_image_tags: rocky-9: 2023.1-rocky-9-20240205T162323 ubuntu-jammy: 2023.1-ubuntu-jammy-20240221T133905 magnum: - rocky-9: 2023.1-rocky-9-20240229T103619 - ubuntu-jammy: 2023.1-ubuntu-jammy-20240229T103619 + rocky-9: 2023.1-rocky-9-20240312T170026 + ubuntu-jammy: 2023.1-ubuntu-jammy-20240312T170026 neutron: rocky-9: 2023.1-rocky-9-20240202T145927 ubuntu-jammy: 2023.1-ubuntu-jammy-20240221T103817 From 27d147c2ffa8690cbb39895cfe77bb8f0a18a85c Mon Sep 17 00:00:00 2001 From: Jake Hutchinson Date: Fri, 8 Mar 2024 15:38:17 +0000 Subject: [PATCH 085/128] Use StackHPC downstream requirements fork --- etc/kayobe/kolla/kolla-build.conf | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/etc/kayobe/kolla/kolla-build.conf b/etc/kayobe/kolla/kolla-build.conf index d88c98ef6..b444eae17 100644 --- a/etc/kayobe/kolla/kolla-build.conf +++ b/etc/kayobe/kolla/kolla-build.conf @@ -9,3 +9,8 @@ base_tag = jammy-20231004 base_tag = 9.{{ stackhpc_pulp_repo_rocky_9_minor_version }} {% endif %} build_args = {{ kolla_build_args.items() | map('join', ':') | join(',') }} + +[openstack-base] +type = git +location = https://github.com/stackhpc/requirements +reference = stackhpc/{{ openstack_release }} From 0ada338662318bfeab8209bc8ce398428f974aa3 Mon Sep 17 00:00:00 2001 From: technowhizz <7688823+technowhizz@users.noreply.github.com> Date: Wed, 13 Mar 2024 16:49:24 +0000 Subject: [PATCH 086/128] Add missing grafana plugins from upstream kolla --- etc/kayobe/kolla.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/etc/kayobe/kolla.yml b/etc/kayobe/kolla.yml index 969d5fb84..49bc7c8a8 100644 --- a/etc/kayobe/kolla.yml +++ b/etc/kayobe/kolla.yml @@ -320,7 +320,9 @@ kolla_build_blocks: ADD additions-archive / grafana_plugins_install: | RUN grafana-cli plugins install vonage-status-panel \ - && grafana-cli plugins install grafana-piechart-panel + && grafana-cli plugins install grafana-piechart-panel \ + && grafana-cli plugins install grafana-opensearch-datasource \ + && grafana-cli plugins install gnocchixyz-gnocchi-datasource ironic_inspector_header: | ADD additions-archive / magnum_base_footer: | From e054b4de9465beb0817f19febcb0fd325537bc91 Mon Sep 17 00:00:00 2001 From: scrungus Date: Wed, 13 Mar 2024 17:17:49 +0000 Subject: [PATCH 087/128] bump tag --- etc/kayobe/kolla/globals.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/etc/kayobe/kolla/globals.yml b/etc/kayobe/kolla/globals.yml index 419b1ba72..03e17ae72 100644 --- a/etc/kayobe/kolla/globals.yml +++ b/etc/kayobe/kolla/globals.yml @@ -20,9 +20,9 @@ kayobe_image_tags: rocky: yoga-20231218T141822 ubuntu: yoga-20231107T165648 magnum: - centos: yoga-20240229T120519 - rocky: yoga-20240229T120519 - ubuntu: yoga-20240229T120519 + centos: yoga-20240308T154440 + rocky: yoga-20240308T154440 + ubuntu: yoga-20240308T154440 neutron: centos: yoga-20231114T125927 rocky: yoga-20240105T120257 From 77a950e9eef966def32c6dd6663005b0fbdd20e5 Mon Sep 17 00:00:00 2001 From: technowhizz <7688823+technowhizz@users.noreply.github.com> Date: Wed, 13 Mar 2024 17:46:49 +0000 Subject: [PATCH 088/128] Bump tags for grafana --- etc/kayobe/kolla-image-tags.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/etc/kayobe/kolla-image-tags.yml b/etc/kayobe/kolla-image-tags.yml index 37a2f0b17..adb956b51 100644 --- a/etc/kayobe/kolla-image-tags.yml +++ b/etc/kayobe/kolla-image-tags.yml @@ -18,3 +18,6 @@ kolla_image_tags: neutron: rocky-9: 2023.1-rocky-9-20240202T145927 ubuntu-jammy: 2023.1-ubuntu-jammy-20240221T103817 + grafana: + rocky-9: 2023.1-rocky-9-20240313T165255 + ubuntu-jammy: 2023.1-ubuntu-jammy-20240313T165255 From fe51c289de298488b4f29fafd928ef120ce7c89f Mon Sep 17 00:00:00 2001 From: technowhizz <7688823+technowhizz@users.noreply.github.com> Date: Wed, 13 Mar 2024 17:49:33 +0000 Subject: [PATCH 089/128] Add release note --- .../notes/add-grafana-plugins-f4856a30529ac686.yaml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 etc/releasenotes/notes/add-grafana-plugins-f4856a30529ac686.yaml diff --git a/etc/releasenotes/notes/add-grafana-plugins-f4856a30529ac686.yaml b/etc/releasenotes/notes/add-grafana-plugins-f4856a30529ac686.yaml new file mode 100644 index 000000000..b4235388b --- /dev/null +++ b/etc/releasenotes/notes/add-grafana-plugins-f4856a30529ac686.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + The grafana image now includes the `gnocchixyz-gnocchi-datasource` and the + `grafana-opensearch-datasource` plugins, which are the default upstream + plugins. From e1f3f8d6f51987f7d4fb874653c698205ef10202 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Fri, 15 Mar 2024 10:25:45 +0000 Subject: [PATCH 090/128] Fix releasenote location --- .../notes/add-grafana-plugins-f4856a30529ac686.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {etc/releasenotes => releasenotes}/notes/add-grafana-plugins-f4856a30529ac686.yaml (100%) diff --git a/etc/releasenotes/notes/add-grafana-plugins-f4856a30529ac686.yaml b/releasenotes/notes/add-grafana-plugins-f4856a30529ac686.yaml similarity index 100% rename from etc/releasenotes/notes/add-grafana-plugins-f4856a30529ac686.yaml rename to releasenotes/notes/add-grafana-plugins-f4856a30529ac686.yaml From f1564f4a7cf324d370ab6d19a6beb8ec71bf2a5c Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Fri, 15 Mar 2024 11:35:08 +0000 Subject: [PATCH 091/128] hotfix: Fix setting containers_list and running without a command --- etc/kayobe/ansible/hotfix-containers.yml | 2 +- etc/kayobe/ansible/run-container-hotfix.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/etc/kayobe/ansible/hotfix-containers.yml b/etc/kayobe/ansible/hotfix-containers.yml index b6a811801..23c28a6b9 100644 --- a/etc/kayobe/ansible/hotfix-containers.yml +++ b/etc/kayobe/ansible/hotfix-containers.yml @@ -30,7 +30,7 @@ - name: Set fact for containers list set_fact: - containers_list: host_containers.stdout + containers_list: "{{ host_containers.stdout }}" - name: Fail if no containers match given regex vars: diff --git a/etc/kayobe/ansible/run-container-hotfix.yml b/etc/kayobe/ansible/run-container-hotfix.yml index 582ade5da..de652e451 100644 --- a/etc/kayobe/ansible/run-container-hotfix.yml +++ b/etc/kayobe/ansible/run-container-hotfix.yml @@ -20,3 +20,4 @@ - name: Run container_hotfix_command command: "{{ kolla_container_engine | default('docker')}} exec {{ '-u 0' if container_hotfix_become else '' }} {{ hotfix_container }} {{ container_hotfix_command }}" + when: container_hotfix_command From 37b387aa3888661b725009e6021676030dbb5bd4 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Fri, 15 Mar 2024 11:40:24 +0000 Subject: [PATCH 092/128] hotfix: Fix failure message --- etc/kayobe/ansible/hotfix-containers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/kayobe/ansible/hotfix-containers.yml b/etc/kayobe/ansible/hotfix-containers.yml index 23c28a6b9..677105f3e 100644 --- a/etc/kayobe/ansible/hotfix-containers.yml +++ b/etc/kayobe/ansible/hotfix-containers.yml @@ -36,7 +36,7 @@ vars: hotfix_containers: "{{ containers_list | split('\n') | regex_search(container_hotfix_container_regex) }}" fail: - msg: "No containers matched. Please check your regex. Containers running on host: {{ host_containers | split('\n') }}" + msg: "No containers matched. Please check your regex. Containers running on host: {{ host_containers.stdout_lines }}" when: hotfix_containers == "" - name: Ensure hotfix-files directory exists on the remote host From d2ed09e11fc58ee3658c53cf983075a72d334bb2 Mon Sep 17 00:00:00 2001 From: Alex-Welsh Date: Mon, 18 Mar 2024 10:40:11 +0000 Subject: [PATCH 093/128] Run OVN playbook without limit during upgrade --- .../ansible/ovn-fix-chassis-priorities.yml | 31 ++++++++++--------- etc/kayobe/ansible/ubuntu-upgrade.yml | 4 --- tools/ubuntu-upgrade-overcloud.sh | 2 ++ 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/etc/kayobe/ansible/ovn-fix-chassis-priorities.yml b/etc/kayobe/ansible/ovn-fix-chassis-priorities.yml index 36566b6a3..9ba469ce7 100644 --- a/etc/kayobe/ansible/ovn-fix-chassis-priorities.yml +++ b/etc/kayobe/ansible/ovn-fix-chassis-priorities.yml @@ -21,22 +21,25 @@ - name: Find OVN DB DB Leader hosts: "{{ ovn_nb_db_group | default('controllers') }}" tasks: - - name: Find the OVN NB DB leader - ansible.builtin.command: docker exec ovn_nb_db ovn-nbctl get-connection - changed_when: false - failed_when: false - register: ovn_check_result - check_mode: false + - name: Find OVN DB Leader + when: kolla_enable_ovn | bool + block: + - name: Find the OVN NB DB leader + ansible.builtin.command: docker exec ovn_nb_db ovn-nbctl get-connection + changed_when: false + failed_when: false + register: ovn_check_result + check_mode: false - - name: Group hosts by leader/follower role - ansible.builtin.group_by: - key: "ovn_nb_{{ 'leader' if ovn_check_result.rc == 0 else 'follower' }}" - changed_when: false + - name: Group hosts by leader/follower role + ansible.builtin.group_by: + key: "ovn_nb_{{ 'leader' if ovn_check_result.rc == 0 else 'follower' }}" + changed_when: false - - name: Assert one leader exists - ansible.builtin.assert: - that: - - groups['ovn_nb_leader'] | default([]) | length == 1 + - name: Assert one leader exists + ansible.builtin.assert: + that: + - groups['ovn_nb_leader'] | default([]) | length == 1 - name: Fix OVN chassis priorities hosts: ovn_nb_leader diff --git a/etc/kayobe/ansible/ubuntu-upgrade.yml b/etc/kayobe/ansible/ubuntu-upgrade.yml index 3b477731c..928e1c52d 100644 --- a/etc/kayobe/ansible/ubuntu-upgrade.yml +++ b/etc/kayobe/ansible/ubuntu-upgrade.yml @@ -104,7 +104,3 @@ that: - ansible_facts.distribution_major_version == '22' - ansible_facts.distribution_release == 'jammy' - -- name: Run the OVN chassis priority fix playbook - import_playbook: "{{ lookup('ansible.builtin.env', 'KAYOBE_CONFIG_PATH') }}/ansible/ovn-fix-chassis-priorities.yml" - when: kolla_enable_ovn diff --git a/tools/ubuntu-upgrade-overcloud.sh b/tools/ubuntu-upgrade-overcloud.sh index 3e351d6d6..50959c263 100755 --- a/tools/ubuntu-upgrade-overcloud.sh +++ b/tools/ubuntu-upgrade-overcloud.sh @@ -31,4 +31,6 @@ set -x kayobe playbook run $KAYOBE_CONFIG_PATH/ansible/ubuntu-upgrade.yml -e os_release=jammy --limit $1 +kayobe playbook run $KAYOBE_CONFIG_PATH/ansible/ovn-fix-chassis-priorities.yml + kayobe overcloud host configure --limit $1 --kolla-limit $1 -e os_release=jammy From 7067c92e460787a013b1a66ea09a4d73fa4a959a Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Mon, 18 Mar 2024 13:42:06 +0000 Subject: [PATCH 094/128] Merge pull request #981 from stackhpc/use-fork-requirements Use StackHPC downstream requirements fork --- etc/kayobe/kolla/kolla-build.conf | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/etc/kayobe/kolla/kolla-build.conf b/etc/kayobe/kolla/kolla-build.conf index 2cd9c7a25..d78d0ebe2 100644 --- a/etc/kayobe/kolla/kolla-build.conf +++ b/etc/kayobe/kolla/kolla-build.conf @@ -9,3 +9,8 @@ base_tag = focal-20231003 base_tag = 9.{{ stackhpc_pulp_repo_rocky_9_minor_version }} {% endif %} build_args = {{ kolla_build_args.items() | map('join', ':') | join(',') }} + +[openstack-base] +type = git +location = https://github.com/stackhpc/requirements +reference = stackhpc/{{ openstack_release }} From 4ee0f70e8de5d9036b1e0c5db8dfe06bd708ca81 Mon Sep 17 00:00:00 2001 From: scrungus Date: Wed, 20 Mar 2024 12:54:01 +0000 Subject: [PATCH 095/128] feature reno --- releasenotes/notes/bump-magnum-capi-helm-6723d89456e6a590.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/bump-magnum-capi-helm-6723d89456e6a590.yaml b/releasenotes/notes/bump-magnum-capi-helm-6723d89456e6a590.yaml index 864edf44b..7fc3cca1a 100644 --- a/releasenotes/notes/bump-magnum-capi-helm-6723d89456e6a590.yaml +++ b/releasenotes/notes/bump-magnum-capi-helm-6723d89456e6a590.yaml @@ -1,4 +1,4 @@ --- -upgrade: +features: - | Updates Magnum CAPI Helm driver version to v0.11.0 From d68a23fe0c7a75c7ac927ac8c6ac1b4c89b6a262 Mon Sep 17 00:00:00 2001 From: Bartosz Bezak Date: Wed, 20 Mar 2024 11:36:13 +0100 Subject: [PATCH 096/128] Update cephadm collection version --- etc/kayobe/ansible/requirements.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/kayobe/ansible/requirements.yml b/etc/kayobe/ansible/requirements.yml index aa28f43f6..92c3faecd 100644 --- a/etc/kayobe/ansible/requirements.yml +++ b/etc/kayobe/ansible/requirements.yml @@ -1,7 +1,7 @@ --- collections: - name: stackhpc.cephadm - version: 1.14.0 + version: 1.15.1 # NOTE: Pinning pulp.squeezer to 0.0.13 because 0.0.14+ depends on the # pulp_glue Python library being installed. - name: pulp.squeezer From aca602e48924e6dc654e6e862af9c44307d862cd Mon Sep 17 00:00:00 2001 From: Seunghun Lee Date: Wed, 20 Mar 2024 13:44:27 +0000 Subject: [PATCH 097/128] Rebuild heat images with yaql 3.0.0 for 2023.1 --- etc/kayobe/kolla-image-tags.yml | 3 +++ .../rebuild-heat-with-yaql-3.0.0-4415d8232bc547df.yaml | 7 +++++++ 2 files changed, 10 insertions(+) create mode 100644 releasenotes/notes/rebuild-heat-with-yaql-3.0.0-4415d8232bc547df.yaml diff --git a/etc/kayobe/kolla-image-tags.yml b/etc/kayobe/kolla-image-tags.yml index adb956b51..a457f41ed 100644 --- a/etc/kayobe/kolla-image-tags.yml +++ b/etc/kayobe/kolla-image-tags.yml @@ -9,6 +9,9 @@ kolla_image_tags: haproxy_ssh: rocky-9: 2023.1-rocky-9-20240205T162323 ubuntu-jammy: 2023.1-ubuntu-jammy-20240221T133905 + heat: + rocky-9: 2023.1-rocky-9-20240319T134201 + ubuntu-jammy: 2023.1-ubuntu-jammy-20240319T134201 letsencrypt: rocky-9: 2023.1-rocky-9-20240205T162323 ubuntu-jammy: 2023.1-ubuntu-jammy-20240221T133905 diff --git a/releasenotes/notes/rebuild-heat-with-yaql-3.0.0-4415d8232bc547df.yaml b/releasenotes/notes/rebuild-heat-with-yaql-3.0.0-4415d8232bc547df.yaml new file mode 100644 index 000000000..da3cb5cbb --- /dev/null +++ b/releasenotes/notes/rebuild-heat-with-yaql-3.0.0-4415d8232bc547df.yaml @@ -0,0 +1,7 @@ +--- +security: + - | + The Heat container images are rebuilt with yaql 3.0.0 to include patch for + vulnerability OSSN/OSSN-0093. It is recommended that you redeploy Heat + services in your system with the current version of Heat images from + StackHPC Release Train. From 9f6a0173b30804b71de4e0fac566cf106b49b89b Mon Sep 17 00:00:00 2001 From: Seunghun Lee Date: Wed, 20 Mar 2024 13:46:29 +0000 Subject: [PATCH 098/128] Rebuild heat images with yaql 3.0.0 for zed --- etc/kayobe/kolla-image-tags.yml | 3 +++ .../rebuild-heat-with-yaql-3.0.0-4415d8232bc547df.yaml | 7 +++++++ 2 files changed, 10 insertions(+) create mode 100644 releasenotes/notes/rebuild-heat-with-yaql-3.0.0-4415d8232bc547df.yaml diff --git a/etc/kayobe/kolla-image-tags.yml b/etc/kayobe/kolla-image-tags.yml index a264fdbf1..c26b70d7d 100644 --- a/etc/kayobe/kolla-image-tags.yml +++ b/etc/kayobe/kolla-image-tags.yml @@ -6,6 +6,9 @@ kolla_image_tags: openstack: rocky-9: zed-rocky-9-20240202T105829 ubuntu-jammy: zed-ubuntu-jammy-20240129T151534 + heat: + rocky-9: zed-rocky-9-20240320T113114 + ubuntu-jammy: zed-ubuntu-jammy-20240320T113114 magnum: rocky-9: zed-rocky-9-20240301T100039 ubuntu-jammy: zed-ubuntu-jammy-20240301T100039 diff --git a/releasenotes/notes/rebuild-heat-with-yaql-3.0.0-4415d8232bc547df.yaml b/releasenotes/notes/rebuild-heat-with-yaql-3.0.0-4415d8232bc547df.yaml new file mode 100644 index 000000000..da3cb5cbb --- /dev/null +++ b/releasenotes/notes/rebuild-heat-with-yaql-3.0.0-4415d8232bc547df.yaml @@ -0,0 +1,7 @@ +--- +security: + - | + The Heat container images are rebuilt with yaql 3.0.0 to include patch for + vulnerability OSSN/OSSN-0093. It is recommended that you redeploy Heat + services in your system with the current version of Heat images from + StackHPC Release Train. From 91cc9ec5883f791067e2538e23581bab76eda8ad Mon Sep 17 00:00:00 2001 From: Seunghun Lee Date: Wed, 20 Mar 2024 13:51:39 +0000 Subject: [PATCH 099/128] Rebuild heat images with yaql 3.0.0 for yoga --- etc/kayobe/kolla/globals.yml | 5 +++++ .../rebuild-heat-with-yaql-3.0.0-4415d8232bc547df.yaml | 7 +++++++ 2 files changed, 12 insertions(+) create mode 100644 releasenotes/notes/rebuild-heat-with-yaql-3.0.0-4415d8232bc547df.yaml diff --git a/etc/kayobe/kolla/globals.yml b/etc/kayobe/kolla/globals.yml index 03e17ae72..e860121d8 100644 --- a/etc/kayobe/kolla/globals.yml +++ b/etc/kayobe/kolla/globals.yml @@ -19,6 +19,10 @@ kayobe_image_tags: centos: yoga-20231107T165648 rocky: yoga-20231218T141822 ubuntu: yoga-20231107T165648 + heat: + centos: yoga-20240320T082414 + rocky: yoga-20240320T082414 + ubuntu: yoga-20240320T082414 magnum: centos: yoga-20240308T154440 rocky: yoga-20240308T154440 @@ -33,6 +37,7 @@ kayobe_image_tags: ubuntu: yoga-20231103T161400 cloudkitty_tag: "{% raw %}{{ kayobe_image_tags['cloudkitty'][kolla_base_distro] }}{% endraw %}" +heat_tag: "{% raw %}{{ kayobe_image_tags['heat'][kolla_base_distro] }}{% endraw %}" magnum_tag: "{% raw %}{{ kayobe_image_tags['magnum'][kolla_base_distro] }}{% endraw %}" neutron_tag: "{% raw %}{{ kayobe_image_tags['neutron'][kolla_base_distro] }}{% endraw %}" nova_tag: "{% raw %}{{ kayobe_image_tags['nova'][kolla_base_distro] }}{% endraw %}" diff --git a/releasenotes/notes/rebuild-heat-with-yaql-3.0.0-4415d8232bc547df.yaml b/releasenotes/notes/rebuild-heat-with-yaql-3.0.0-4415d8232bc547df.yaml new file mode 100644 index 000000000..da3cb5cbb --- /dev/null +++ b/releasenotes/notes/rebuild-heat-with-yaql-3.0.0-4415d8232bc547df.yaml @@ -0,0 +1,7 @@ +--- +security: + - | + The Heat container images are rebuilt with yaql 3.0.0 to include patch for + vulnerability OSSN/OSSN-0093. It is recommended that you redeploy Heat + services in your system with the current version of Heat images from + StackHPC Release Train. From c444a605d2f3f639f64c59188f99f2a8cac16235 Mon Sep 17 00:00:00 2001 From: Scott Davidson <49713135+sd109@users.noreply.github.com> Date: Wed, 20 Mar 2024 17:11:56 +0000 Subject: [PATCH 100/128] Update Magnum CAPI Helm driver version (#1007) * Bump magnum-capi-helm driver to v0.12.0 * Update magnum image tags * Add release note --------- --- etc/kayobe/kolla-image-tags.yml | 4 ++-- etc/kayobe/kolla.yml | 2 +- .../notes/bump-magnum-capi-helm-6febfe840e81cea5.yaml | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/bump-magnum-capi-helm-6febfe840e81cea5.yaml diff --git a/etc/kayobe/kolla-image-tags.yml b/etc/kayobe/kolla-image-tags.yml index adb956b51..e2692efd3 100644 --- a/etc/kayobe/kolla-image-tags.yml +++ b/etc/kayobe/kolla-image-tags.yml @@ -13,8 +13,8 @@ kolla_image_tags: rocky-9: 2023.1-rocky-9-20240205T162323 ubuntu-jammy: 2023.1-ubuntu-jammy-20240221T133905 magnum: - rocky-9: 2023.1-rocky-9-20240312T170026 - ubuntu-jammy: 2023.1-ubuntu-jammy-20240312T170026 + rocky-9: 2023.1-rocky-9-20240320T133822 + ubuntu-jammy: 2023.1-ubuntu-jammy-20240320T133822 neutron: rocky-9: 2023.1-rocky-9-20240202T145927 ubuntu-jammy: 2023.1-ubuntu-jammy-20240221T103817 diff --git a/etc/kayobe/kolla.yml b/etc/kayobe/kolla.yml index 6bcbdc09a..6db29a0cc 100644 --- a/etc/kayobe/kolla.yml +++ b/etc/kayobe/kolla.yml @@ -328,7 +328,7 @@ kolla_build_blocks: magnum_base_footer: | RUN curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | head -n -1 | bash {% raw %} - {% set magnum_capi_packages = ['git+https://github.com/stackhpc/magnum-capi-helm.git@v0.11.0'] %} + {% set magnum_capi_packages = ['git+https://github.com/stackhpc/magnum-capi-helm.git@v0.12.0'] %} RUN {{ macros.install_pip(magnum_capi_packages | customizable("pip_packages")) }} {% endraw %} # Dict mapping image customization variable names to their values. diff --git a/releasenotes/notes/bump-magnum-capi-helm-6febfe840e81cea5.yaml b/releasenotes/notes/bump-magnum-capi-helm-6febfe840e81cea5.yaml new file mode 100644 index 000000000..6677583fb --- /dev/null +++ b/releasenotes/notes/bump-magnum-capi-helm-6febfe840e81cea5.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Updates Magnum CAPI Helm driver version to v0.12.0 From 9beb7fb09bb5c51a9261e7bed73564378f5e854e Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Thu, 21 Mar 2024 13:18:05 +0000 Subject: [PATCH 101/128] Fail on any unparsed Ansible inventory If Ansible is unable to parse an inventory source, by default it will print a warning and continue execution. Typically this highlights an important error that should be addressed. This change modifies the Ansible configuration to error out in this case. --- etc/kayobe/ansible.cfg | 4 ++++ .../notes/fail-unparsed-inventory-c3b4e2ffcb620a6b.yaml | 7 +++++++ 2 files changed, 11 insertions(+) create mode 100644 releasenotes/notes/fail-unparsed-inventory-c3b4e2ffcb620a6b.yaml diff --git a/etc/kayobe/ansible.cfg b/etc/kayobe/ansible.cfg index 901001ad8..b38cb8239 100644 --- a/etc/kayobe/ansible.cfg +++ b/etc/kayobe/ansible.cfg @@ -11,5 +11,9 @@ callbacks_enabled = ansible.posix.profile_tasks # Silence warning about invalid characters found in group names force_valid_group_names = ignore +[inventory] +# Fail when any inventory source cannot be parsed. +any_unparsed_is_failed = True + [ssh_connection] pipelining = True diff --git a/releasenotes/notes/fail-unparsed-inventory-c3b4e2ffcb620a6b.yaml b/releasenotes/notes/fail-unparsed-inventory-c3b4e2ffcb620a6b.yaml new file mode 100644 index 000000000..335691c30 --- /dev/null +++ b/releasenotes/notes/fail-unparsed-inventory-c3b4e2ffcb620a6b.yaml @@ -0,0 +1,7 @@ +--- +upgrade: + - | + Updates the Ansible configuration to `fail on any unparsed inventory source + `__. + If you are using a separate Ansible configuration for Kolla Ansible, you + may wish to add this setting in ``etc/kayobe/kolla/ansible.cfg``. From 1fd719094c633a48abf73e283deacf55cfd47214 Mon Sep 17 00:00:00 2001 From: Scott Davidson <49713135+sd109@users.noreply.github.com> Date: Fri, 22 Mar 2024 09:28:56 +0000 Subject: [PATCH 102/128] Update docs to reflect upstream Magnum driver changes (#1000) --- doc/source/configuration/magnum-capi.rst | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/doc/source/configuration/magnum-capi.rst b/doc/source/configuration/magnum-capi.rst index 302bf800c..c05a80bcf 100644 --- a/doc/source/configuration/magnum-capi.rst +++ b/doc/source/configuration/magnum-capi.rst @@ -105,16 +105,10 @@ Next, copy the CAPI management cluster's kubeconfig file into your stackhpc-kayo The following config should also be set in your stackhpc-kayobe-config environment: -.. code-block:: ini - :caption: magnum.conf - - [nova_client] - endpoint_type = publicURL - .. code-block:: yaml :caption: kolla/globals.yml - magnum_cluster_api_driver_enabled: true + magnum_capi_helm_driver_enabled: true To apply the configuration, run ``kayobe overcloud service reconfigure -kt magnum``. From 99838a89987a6c4250f4eb0a55ed7d199853b5fe Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Tue, 26 Mar 2024 12:42:46 +0000 Subject: [PATCH 103/128] docs: Add an upgrade doc note about Glance show_multiple_locations Operators may decide to replace this option. --- doc/source/operations/upgrading.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/doc/source/operations/upgrading.rst b/doc/source/operations/upgrading.rst index 53df5aef2..d44f1e917 100644 --- a/doc/source/operations/upgrading.rst +++ b/doc/source/operations/upgrading.rst @@ -94,6 +94,20 @@ custom configuration that uses the Keystone admin port. One such example is the config for Ceph RGW in ``etc/kayobe/cephadm.yml``. Be sure to update any manual references to the old port. +Glance show_multiple_locations disabled +--------------------------------------- + +Kolla Ansible no longer sets ``show_multiple_locations = True`` in Glance by +default when Glance's Ceph RBD backend is enabled. This was applied as a fix +but operators must note that this, in turn, disables Cinder and Nova's +optimisations. In particular, this can increase instance creation times due to +a lack of copy-on-write. + +On the other hand, these optimisations might have been causing other trouble +for operators. Please see `LP#1992153 +`__. Operators relying +on this feature can set the flag themselves using service config overrides. + Known issues ============ From 8f6fcb81af1f53355056ed511d2b18860a2d2125 Mon Sep 17 00:00:00 2001 From: Alex-Welsh Date: Thu, 28 Mar 2024 14:15:23 +0000 Subject: [PATCH 104/128] Fix host image builds on Arc runners Arc runners are kubernetes-orchestrated github runners. Host image builds do not work on these runners, so this commit adapts the host image build workflow to spin up a worker VM which executes the build. --- .../workflows/overcloud-host-image-build.yml | 400 +++++++++++------- .github/workflows/stackhpc-ci-cleanup.yml | 20 + .../ansible/openstack-host-image-upload.yml | 54 +++ etc/kayobe/ansible/pulp-host-image-upload.yml | 16 +- .../environments/ci-builder/inventory/hosts | 2 +- etc/kayobe/overcloud-dib.yml | 2 +- 6 files changed, 323 insertions(+), 171 deletions(-) create mode 100644 etc/kayobe/ansible/openstack-host-image-upload.yml diff --git a/.github/workflows/overcloud-host-image-build.yml b/.github/workflows/overcloud-host-image-build.yml index 4c338cda3..1b6c2e5f9 100644 --- a/.github/workflows/overcloud-host-image-build.yml +++ b/.github/workflows/overcloud-host-image-build.yml @@ -35,21 +35,38 @@ on: env: ANSIBLE_FORCE_COLOR: True + KAYOBE_ENVIRONMENT: ci-builder + KAYOBE_VAULT_PASSWORD: ${{ secrets.KAYOBE_VAULT_PASSWORD }} jobs: overcloud-host-image-build: name: Build overcloud host images if: github.repository == 'stackhpc/stackhpc-kayobe-config' - runs-on: [self-hosted, stackhpc-kayobe-config-kolla-builder] + runs-on: arc-skc-host-image-builder-runner permissions: {} steps: - - uses: actions/checkout@v4 + - name: Install Package + uses: ConorMacBride/install-package@main + with: + apt: git unzip nodejs python3-pip python3-venv openssh-server openssh-client jq + + - name: Start the SSH service + run: | + sudo /etc/init.d/ssh start + + - name: Checkout + uses: actions/checkout@v4 with: path: src/kayobe-config + - name: Output image tag of the builder + id: builder_image_tag + run: | + echo image_tag=$(grep stackhpc_rocky_9_overcloud_host_image_version: etc/kayobe/pulp-host-image-versions.yml | awk '{print $2}') >> $GITHUB_OUTPUT + - name: Determine OpenStack release id: openstack_release run: | - BRANCH=$(awk -F'=' '/defaultbranch/ {print $2}' .gitreview) + BRANCH=$(awk -F'=' '/defaultbranch/ {print $2}' src/kayobe-config/.gitreview) echo "openstack_release=${BRANCH}" | sed -E "s,(stable|unmaintained)/,," >> $GITHUB_OUTPUT # Generate a tag to apply to all built overcloud host images. @@ -62,10 +79,6 @@ jobs: run: | echo "${{ steps.host_image_tag.outputs.host_image_tag }}" - - name: Clean any previous build artifact - run: | - rm -f /tmp/updated_images.txt - - name: Clone StackHPC Kayobe repository uses: actions/checkout@v4 with: @@ -73,34 +86,6 @@ jobs: ref: refs/heads/stackhpc/${{ steps.openstack_release.outputs.openstack_release }} path: src/kayobe - # FIXME: Failed in kolla-ansible : Ensure the latest version of pip is installed - - name: Install dependencies - run: | - sudo dnf -y install python3-virtualenv zstd - - - name: Setup networking - run: | - if ! ip l show breth1 >/dev/null 2>&1; then - sudo ip l add breth1 type bridge - fi - sudo ip l set breth1 up - if ! ip a show breth1 | grep 192.168.33.3/24; then - sudo ip a add 192.168.33.3/24 dev breth1 - fi - if ! ip l show dummy1 >/dev/null 2>&1; then - sudo ip l add dummy1 type dummy - fi - sudo ip l set dummy1 up - sudo ip l set dummy1 master breth1 - - # FIXME: Without this workaround we see the following issue after the runner is power cycled: - # TASK [MichaelRigart.interfaces : RedHat | ensure network service is started and enabled] *** - # Unable to start service network: Job for network.service failed because the control process exited with error code. - # See \"systemctl status network.service\" and \"journalctl -xe\" for details. - - name: Kill dhclient (workaround) - run: | - (sudo killall dhclient || true) && sudo systemctl restart network - - name: Install Kayobe run: | mkdir -p venvs && @@ -110,36 +95,132 @@ jobs: pip install -U pip && pip install ../src/kayobe + - name: Install terraform + uses: hashicorp/setup-terraform@v2 + + - name: Initialise terraform + run: terraform init + working-directory: ${{ github.workspace }}/src/kayobe-config/terraform/aio + + - name: Generate SSH keypair + run: ssh-keygen -f id_rsa -N '' + working-directory: ${{ github.workspace }}/src/kayobe-config/terraform/aio + + - name: Generate clouds.yaml + run: | + cat << EOF > clouds.yaml + ${{ secrets.CLOUDS_YAML }} + EOF + working-directory: ${{ github.workspace }}/src/kayobe-config/terraform/aio + + - name: Generate terraform.tfvars + run: | + cat << EOF > terraform.tfvars + ssh_public_key = "id_rsa.pub" + ssh_username = "rocky" + aio_vm_name = "skc-host-image-builder" + # Must be a Rocky Linux 9 host to successfully build all images + # This MUST NOT be an LVM image. It can cause confusing conficts with the built image. + aio_vm_image = "Rocky-9-GenericCloud-Base-9.3-20231113.0.x86_64.qcow2" + aio_vm_flavor = "en1.medium" + aio_vm_network = "stackhpc-ci" + aio_vm_subnet = "stackhpc-ci" + aio_vm_interface = "eth0" + EOF + working-directory: ${{ github.workspace }}/src/kayobe-config/terraform/aio + + - name: Terraform Plan + run: terraform plan + working-directory: ${{ github.workspace }}/src/kayobe-config/terraform/aio + env: + OS_CLOUD: "openstack" + OS_APPLICATION_CREDENTIAL_ID: ${{ secrets.OS_APPLICATION_CREDENTIAL_ID }} + OS_APPLICATION_CREDENTIAL_SECRET: ${{ secrets.OS_APPLICATION_CREDENTIAL_SECRET }} + + - name: Terraform Apply + run: | + for attempt in $(seq 5); do + if terraform apply -auto-approve; then + echo "Created infrastructure on attempt $attempt" + exit 0 + fi + echo "Failed to create infrastructure on attempt $attempt" + sleep 10 + terraform destroy -auto-approve + sleep 60 + done + echo "Failed to create infrastructure after $attempt attempts" + exit 1 + working-directory: ${{ github.workspace }}/src/kayobe-config/terraform/aio + env: + OS_CLOUD: "openstack" + OS_APPLICATION_CREDENTIAL_ID: ${{ secrets.OS_APPLICATION_CREDENTIAL_ID }} + OS_APPLICATION_CREDENTIAL_SECRET: ${{ secrets.OS_APPLICATION_CREDENTIAL_SECRET }} + + - name: Get Terraform outputs + id: tf_outputs + run: | + terraform output -json + working-directory: ${{ github.workspace }}/src/kayobe-config/terraform/aio + + - name: Write Terraform outputs + run: | + cat << EOF > src/kayobe-config/etc/kayobe/environments/ci-builder/tf-outputs.yml + ${{ steps.tf_outputs.outputs.stdout }} + EOF + + - name: Write Terraform network config + run: | + cat << EOF > src/kayobe-config/etc/kayobe/environments/ci-builder/tf-network-allocation.yml + --- + aio_ips: + builder: "{{ access_ip_v4.value }}" + EOF + + - name: Write Terraform network interface config + run: | + mkdir -p src/kayobe-config/etc/kayobe/environments/$KAYOBE_ENVIRONMENT/inventory/group_vars/seed + rm -f src/kayobe-config/etc/kayobe/environments/$KAYOBE_ENVIRONMENT/inventory/group_vars/seed/network-interfaces + cat << EOF > src/kayobe-config/etc/kayobe/environments/$KAYOBE_ENVIRONMENT/inventory/group_vars/seed/network-interfaces + admin_interface: "{{ access_interface.value }}" + aio_interface: "{{ access_interface.value }}" + EOF + + - name: Manage SSH keys + run: | + mkdir -p ~/.ssh + touch ~/.ssh/authorized_keys + cat src/kayobe-config/terraform/aio/id_rsa.pub >> ~/.ssh/authorized_keys + cp src/kayobe-config/terraform/aio/id_rsa* ~/.ssh/ + - name: Bootstrap the control host run: | source venvs/kayobe/bin/activate && source src/kayobe-config/kayobe-env --environment ci-builder && kayobe control host bootstrap - - name: Configure the seed host + - name: Configure the seed host (Builder VM) run: | source venvs/kayobe/bin/activate && source src/kayobe-config/kayobe-env --environment ci-builder && - kayobe seed host configure + kayobe seed host configure -e seed_bootstrap_user=rocky --skip-tags network + + - name: Install dependencies + run: | + source venvs/kayobe/bin/activate && + source src/kayobe-config/kayobe-env --environment ci-builder && + kayobe seed host command run \ + --command "sudo dnf config-manager --set-enabled crb && sudo dnf -y install epel-release && sudo dnf -y install zstd debootstrap kpartx cloud-init" --show-output env: KAYOBE_VAULT_PASSWORD: ${{ secrets.KAYOBE_VAULT_PASSWORD }} - name: Create bifrost_httpboot Docker volume - run: | - if [[ $(sudo docker volume ls -f Name=bifrost_httpboot -q | wc -l) = 0 ]]; then - sudo docker volume create bifrost_httpboot - fi - - - name: Generate clouds.yaml - run: | - cat << EOF > clouds.yaml - ${{ secrets.CLOUDS_YAML }} - EOF - - - name: Install OpenStack client run: | source venvs/kayobe/bin/activate && - pip install python-openstackclient -c https://releases.openstack.org/constraints/upper/${{ steps.openstack_release.outputs.openstack_release }} + source src/kayobe-config/kayobe-env --environment ci-builder && + kayobe seed host command run --command "sudo mkdir -p /var/lib/docker/volumes/bifrost_httpboot/_data" --show-output + env: + KAYOBE_VAULT_PASSWORD: ${{ secrets.KAYOBE_VAULT_PASSWORD }} - name: Build a CentOS Stream 8 overcloud host image id: build_centos_stream_8 @@ -155,6 +236,16 @@ jobs: KAYOBE_VAULT_PASSWORD: ${{ secrets.KAYOBE_VAULT_PASSWORD }} if: inputs.centos + - name: Show last error logs + continue-on-error: true + run: | + source venvs/kayobe/bin/activate && + source src/kayobe-config/kayobe-env --environment ci-builder && + kayobe seed host command run --command "tail -200 /opt/kayobe/images/overcloud-centos-8-stream/overcloud-centos-8-stream.stdout" --show-output + env: + KAYOBE_VAULT_PASSWORD: ${{ secrets.KAYOBE_VAULT_PASSWORD }} + if: steps.build_centos_stream_8.outcome == 'failure' + - name: Upload CentOS Stream 8 overcloud host image to Ark run: | source venvs/kayobe/bin/activate && @@ -169,18 +260,16 @@ jobs: KAYOBE_VAULT_PASSWORD: ${{ secrets.KAYOBE_VAULT_PASSWORD }} if: inputs.centos && steps.build_centos_stream_8.outcome == 'success' - - name: Upload CentOS Stream 8 overcloud host image to SMS + - name: Upload CentOS Stream 8 overcloud host image to Dev Cloud run: | source venvs/kayobe/bin/activate && - openstack image create \ - overcloud-centos-8-stream-${{ steps.host_image_tag.outputs.host_image_tag }} \ - --container-format bare \ - --disk-format qcow2 \ - --file /opt/kayobe/images/overcloud-centos-8-stream/overcloud-centos-8-stream.qcow2 \ - --private \ - --os-cloud sms-lab-release \ - --progress + source src/kayobe-config/kayobe-env --environment ci-builder && + kayobe playbook run \ + src/kayobe-config/etc/kayobe/ansible/openstack-host-image-upload.yml \ + -e local_image_path="/opt/kayobe/images/overcloud-centos-8-stream/overcloud-centos-8-stream.qcow2" \ + -e image_name=overcloud-centos-8-stream-${{ steps.host_image_tag.outputs.host_image_tag }} env: + CLOUDS_YAML: ${{ secrets.CLOUDS_YAML }} OS_APPLICATION_CREDENTIAL_ID: ${{ secrets.OS_APPLICATION_CREDENTIAL_ID }} OS_APPLICATION_CREDENTIAL_SECRET: ${{ secrets.OS_APPLICATION_CREDENTIAL_SECRET }} if: inputs.centos && steps.build_centos_stream_8.outcome == 'success' @@ -199,6 +288,16 @@ jobs: KAYOBE_VAULT_PASSWORD: ${{ secrets.KAYOBE_VAULT_PASSWORD }} if: inputs.rocky8 + - name: Show last error logs + continue-on-error: true + run: | + source venvs/kayobe/bin/activate && + source src/kayobe-config/kayobe-env --environment ci-builder && + kayobe seed host command run --command "tail -200 /opt/kayobe/images/overcloud-rocky-8/overcloud-rocky-8.stdout" --show-output + env: + KAYOBE_VAULT_PASSWORD: ${{ secrets.KAYOBE_VAULT_PASSWORD }} + if: steps.build_rocky_8.outcome == 'failure' + - name: Upload Rocky Linux 8 overcloud host image to Ark run: | source venvs/kayobe/bin/activate && @@ -212,19 +311,17 @@ jobs: env: KAYOBE_VAULT_PASSWORD: ${{ secrets.KAYOBE_VAULT_PASSWORD }} if: inputs.rocky8 && steps.build_rocky_8.outcome == 'success' - - - name: Upload Rocky Linux 8 overcloud host image to SMS + + - name: Upload Rocky Linux 8 overcloud host image to Dev Cloud run: | source venvs/kayobe/bin/activate && - openstack image create \ - overcloud-rocky-8-${{ steps.host_image_tag.outputs.host_image_tag }} \ - --container-format bare \ - --disk-format qcow2 \ - --file /opt/kayobe/images/overcloud-rocky-8/overcloud-rocky-8.qcow2 \ - --private \ - --os-cloud sms-lab-release \ - --progress + source src/kayobe-config/kayobe-env --environment ci-builder && + kayobe playbook run \ + src/kayobe-config/etc/kayobe/ansible/openstack-host-image-upload.yml \ + -e local_image_path="/opt/kayobe/images/overcloud-rocky-8/overcloud-rocky-8.qcow2" \ + -e image_name=overcloud-rocky-8-${{ steps.host_image_tag.outputs.host_image_tag }} env: + CLOUDS_YAML: ${{ secrets.CLOUDS_YAML }} OS_APPLICATION_CREDENTIAL_ID: ${{ secrets.OS_APPLICATION_CREDENTIAL_ID }} OS_APPLICATION_CREDENTIAL_SECRET: ${{ secrets.OS_APPLICATION_CREDENTIAL_SECRET }} if: inputs.rocky8 && steps.build_rocky_8.outcome == 'success' @@ -243,6 +340,16 @@ jobs: KAYOBE_VAULT_PASSWORD: ${{ secrets.KAYOBE_VAULT_PASSWORD }} if: inputs.rocky9 + - name: Show last error logs + continue-on-error: true + run: | + source venvs/kayobe/bin/activate && + source src/kayobe-config/kayobe-env --environment ci-builder && + kayobe seed host command run --command "tail -200 /opt/kayobe/images/overcloud-rocky-9/overcloud-rocky-9.stdout" --show-output + env: + KAYOBE_VAULT_PASSWORD: ${{ secrets.KAYOBE_VAULT_PASSWORD }} + if: steps.build_rocky_9.outcome == 'failure' + - name: Upload Rocky Linux 9 overcloud host image to Ark run: | source venvs/kayobe/bin/activate && @@ -256,19 +363,17 @@ jobs: env: KAYOBE_VAULT_PASSWORD: ${{ secrets.KAYOBE_VAULT_PASSWORD }} if: inputs.rocky9 && steps.build_rocky_9.outcome == 'success' - - - name: Upload Rocky Linux 9 overcloud host image to SMS + + - name: Upload Rocky Linux 9 overcloud host image to Dev Cloud run: | source venvs/kayobe/bin/activate && - openstack image create \ - overcloud-rocky-9-${{ steps.host_image_tag.outputs.host_image_tag }} \ - --container-format bare \ - --disk-format qcow2 \ - --file /opt/kayobe/images/overcloud-rocky-9/overcloud-rocky-9.qcow2 \ - --private \ - --os-cloud sms-lab-release \ - --progress + source src/kayobe-config/kayobe-env --environment ci-builder && + kayobe playbook run \ + src/kayobe-config/etc/kayobe/ansible/openstack-host-image-upload.yml \ + -e local_image_path="/opt/kayobe/images/overcloud-rocky-9/overcloud-rocky-9.qcow2" \ + -e image_name=overcloud-rocky-9-${{ steps.host_image_tag.outputs.host_image_tag }} env: + CLOUDS_YAML: ${{ secrets.CLOUDS_YAML }} OS_APPLICATION_CREDENTIAL_ID: ${{ secrets.OS_APPLICATION_CREDENTIAL_ID }} OS_APPLICATION_CREDENTIAL_SECRET: ${{ secrets.OS_APPLICATION_CREDENTIAL_SECRET }} if: inputs.rocky9 && steps.build_rocky_9.outcome == 'success' @@ -287,6 +392,16 @@ jobs: KAYOBE_VAULT_PASSWORD: ${{ secrets.KAYOBE_VAULT_PASSWORD }} if: inputs.ubuntu-focal + - name: Show last error logs + continue-on-error: true + run: | + source venvs/kayobe/bin/activate && + source src/kayobe-config/kayobe-env --environment ci-builder && + kayobe seed host command run --command "tail -200 /opt/kayobe/images/overcloud-ubuntu-focal/overcloud-ubuntu-focal.stdout" --show-output + env: + KAYOBE_VAULT_PASSWORD: ${{ secrets.KAYOBE_VAULT_PASSWORD }} + if: steps.build_ubuntu_focal.outcome == 'failure' + - name: Upload Ubuntu Focal 20.04 overcloud host image to Ark run: | source venvs/kayobe/bin/activate && @@ -301,18 +416,16 @@ jobs: KAYOBE_VAULT_PASSWORD: ${{ secrets.KAYOBE_VAULT_PASSWORD }} if: inputs.ubuntu-focal && steps.build_ubuntu_focal.outcome == 'success' - - name: Upload Ubuntu Focal 20.04 overcloud host image to SMS + - name: Upload Ubuntu Focal overcloud host image to Dev Cloud run: | source venvs/kayobe/bin/activate && - openstack image create \ - overcloud-ubuntu-focal-${{ steps.host_image_tag.outputs.host_image_tag }} \ - --container-format bare \ - --disk-format qcow2 \ - --file /opt/kayobe/images/overcloud-ubuntu-focal/overcloud-ubuntu-focal.qcow2 \ - --private \ - --os-cloud sms-lab-release \ - --progress + source src/kayobe-config/kayobe-env --environment ci-builder && + kayobe playbook run \ + src/kayobe-config/etc/kayobe/ansible/openstack-host-image-upload.yml \ + -e local_image_path="/opt/kayobe/images/overcloud-ubuntu-focal/overcloud-ubuntu-focal.qcow2" \ + -e image_name=overcloud-ubuntu-focal-${{ steps.host_image_tag.outputs.host_image_tag }} env: + CLOUDS_YAML: ${{ secrets.CLOUDS_YAML }} OS_APPLICATION_CREDENTIAL_ID: ${{ secrets.OS_APPLICATION_CREDENTIAL_ID }} OS_APPLICATION_CREDENTIAL_SECRET: ${{ secrets.OS_APPLICATION_CREDENTIAL_SECRET }} if: inputs.ubuntu-focal && steps.build_ubuntu_focal.outcome == 'success' @@ -331,6 +444,16 @@ jobs: KAYOBE_VAULT_PASSWORD: ${{ secrets.KAYOBE_VAULT_PASSWORD }} if: inputs.ubuntu-jammy + - name: Show last error logs + continue-on-error: true + run: | + source venvs/kayobe/bin/activate && + source src/kayobe-config/kayobe-env --environment ci-builder && + kayobe seed host command run --command "tail -200 /opt/kayobe/images/overcloud-ubuntu-jammy/overcloud-ubuntu-jammy.stdout" --show-output + env: + KAYOBE_VAULT_PASSWORD: ${{ secrets.KAYOBE_VAULT_PASSWORD }} + if: steps.build_ubuntu_jammy.outcome == 'failure' + - name: Upload Ubuntu Jammy 22.04 overcloud host image to Ark run: | source venvs/kayobe/bin/activate && @@ -345,83 +468,27 @@ jobs: KAYOBE_VAULT_PASSWORD: ${{ secrets.KAYOBE_VAULT_PASSWORD }} if: inputs.ubuntu-jammy && steps.build_ubuntu_jammy.outcome == 'success' - - name: Upload Ubuntu Jammy 22.04 overcloud host image to SMS + - name: Upload Ubuntu Jammy overcloud host image to Dev Cloud run: | source venvs/kayobe/bin/activate && - openstack image create \ - overcloud-ubuntu-jammy-${{ steps.host_image_tag.outputs.host_image_tag }} \ - --container-format bare \ - --disk-format qcow2 \ - --file /opt/kayobe/images/overcloud-ubuntu-jammy/overcloud-ubuntu-jammy.qcow2 \ - --private \ - --os-cloud sms-lab-release \ - --progress + source src/kayobe-config/kayobe-env --environment ci-builder && + kayobe playbook run \ + src/kayobe-config/etc/kayobe/ansible/openstack-host-image-upload.yml \ + -e local_image_path="/opt/kayobe/images/overcloud-ubuntu-jammy/overcloud-ubuntu-jammy.qcow2" \ + -e image_name=overcloud-ubuntu-jammy-${{ steps.host_image_tag.outputs.host_image_tag }} env: + CLOUDS_YAML: ${{ secrets.CLOUDS_YAML }} OS_APPLICATION_CREDENTIAL_ID: ${{ secrets.OS_APPLICATION_CREDENTIAL_ID }} OS_APPLICATION_CREDENTIAL_SECRET: ${{ secrets.OS_APPLICATION_CREDENTIAL_SECRET }} if: inputs.ubuntu-jammy && steps.build_ubuntu_jammy.outcome == 'success' - - name: Upload updated images artifact - uses: actions/upload-artifact@v4 - with: - name: Updated images list - path: /tmp/updated_images.txt - retention-days: 7 - if: steps.build_centos_stream_8.outcome == 'success' || - steps.build_rocky_8.outcome == 'success' || - steps.build_rocky_9.outcome == 'success' || - steps.build_ubuntu_focal.outcome == 'success' || - steps.build_ubuntu_jammy.outcome == 'success' - - - name: Upload CentOS build logs if build failed - uses: actions/upload-artifact@v4 - with: - name: CentOS build logs - path: | - /opt/kayobe/images/overcloud-centos-8-stream/overcloud-centos-8-stream.stdout - /opt/kayobe/images/overcloud-centos-8-stream/overcloud-centos-8-stream.stderr - retention-days: 7 - if: steps.build_centos_stream_8.outcome == 'failure' - - - name: Upload Rocky 8 build logs if build failed - uses: actions/upload-artifact@v4 - with: - name: Rocky 8 build logs - path: | - /opt/kayobe/images/overcloud-rocky-8/overcloud-rocky-8.stdout - /opt/kayobe/images/overcloud-rocky-8/overcloud-rocky-8.stderr - retention-days: 7 - if: steps.build_rocky_8.outcome == 'failure' - - - name: Upload Rocky 9 build logs if build failed - uses: actions/upload-artifact@v4 - with: - name: Rocky 9 build logs - path: | - /opt/kayobe/images/overcloud-rocky-9/overcloud-rocky-9.stdout - /opt/kayobe/images/overcloud-rocky-9/overcloud-rocky-9.stderr - retention-days: 7 - if: steps.build_rocky_9.outcome == 'failure' - - - name: Upload Ubuntu Focal 20.04 build logs if build failed - uses: actions/upload-artifact@v4 - with: - name: Ubuntu Focal 20.04 build logs - path: | - /opt/kayobe/images/overcloud-ubuntu-focal/overcloud-ubuntu-focal.stdout - /opt/kayobe/images/overcloud-ubuntu-focal/overcloud-ubuntu-focal.stderr - retention-days: 7 - if: steps.build_ubuntu_focal.outcome == 'failure' - - - name: Upload Ubuntu Jammy 22.04 build logs if build failed - uses: actions/upload-artifact@v4 - with: - name: Ubuntu Jammy 22.04 build logs - path: | - /opt/kayobe/images/overcloud-ubuntu-jammy/overcloud-ubuntu-jammy.stdout - /opt/kayobe/images/overcloud-ubuntu-jammy/overcloud-ubuntu-jammy.stderr - retention-days: 7 - if: steps.build_ubuntu_jammy.outcome == 'failure' + - name: Copy logs back + continue-on-error: true + run: | + mkdir logs + scp -r rocky@$(jq -r .access_ip_v4.value src/kayobe-config/etc/kayobe/environments/ci-builder/tf-outputs.yml):/opt/kayobe/images/*/*.std* ./logs/ + scp -r rocky@$(jq -r .access_ip_v4.value src/kayobe-config/etc/kayobe/environments/ci-builder/tf-outputs.yml):/tmp/updated_images.txt ./logs/ || true + if: always() - name: Fail if any overcloud host image builds failed run: | @@ -433,7 +500,18 @@ jobs: steps.build_ubuntu_focal.outcome == 'failure' || steps.build_ubuntu_jammy.outcome == 'failure' - - name: Clean up build artifacts - run: | - sudo rm -rf /opt/kayobe/images/ + - name: Upload logs artifact + uses: actions/upload-artifact@v4 + with: + name: Build logs + path: ./logs if: always() + + - name: Destroy + run: terraform destroy -auto-approve + working-directory: ${{ github.workspace }}/src/kayobe-config/terraform/aio + env: + OS_CLOUD: openstack + OS_APPLICATION_CREDENTIAL_ID: ${{ secrets.OS_APPLICATION_CREDENTIAL_ID }} + OS_APPLICATION_CREDENTIAL_SECRET: ${{ secrets.OS_APPLICATION_CREDENTIAL_SECRET }} + if: always() \ No newline at end of file diff --git a/.github/workflows/stackhpc-ci-cleanup.yml b/.github/workflows/stackhpc-ci-cleanup.yml index a769aa718..ed9ec327c 100644 --- a/.github/workflows/stackhpc-ci-cleanup.yml +++ b/.github/workflows/stackhpc-ci-cleanup.yml @@ -55,3 +55,23 @@ jobs: OS_CLOUD: openstack OS_APPLICATION_CREDENTIAL_ID: ${{ secrets.OS_APPLICATION_CREDENTIAL_ID }} OS_APPLICATION_CREDENTIAL_SECRET: ${{ secrets.OS_APPLICATION_CREDENTIAL_SECRET }} + + - name: Clean up host image builder instances over 5 hours old + run: | + result=0 + changes_before=$(date -Imin -d -5hours) + for status in ACTIVE BUILD ERROR SHUTOFF; do + for instance in $(openstack server list --tags skc-host-image-build --os-compute-api-version 2.66 --format value --column ID --changes-before $changes_before --status $status); do + echo "Cleaning up $status instance $instance" + openstack server show $instance + if ! openstack server delete $instance; then + echo "Failed to delete $status instance $instance" + result=1 + fi + done + done + exit $result + env: + OS_CLOUD: openstack + OS_APPLICATION_CREDENTIAL_ID: ${{ secrets.OS_APPLICATION_CREDENTIAL_ID }} + OS_APPLICATION_CREDENTIAL_SECRET: ${{ secrets.OS_APPLICATION_CREDENTIAL_SECRET }} diff --git a/etc/kayobe/ansible/openstack-host-image-upload.yml b/etc/kayobe/ansible/openstack-host-image-upload.yml new file mode 100644 index 000000000..2c92d2446 --- /dev/null +++ b/etc/kayobe/ansible/openstack-host-image-upload.yml @@ -0,0 +1,54 @@ +--- +# This playbook is designed to be used by the overcloud-host-image-build.yml +# GitHub workflow to upload newly-built images to a development cloud for +# testing and use in CI. +- name: Upload an OS image to Glance + hosts: seed + vars: + local_image_path: "/opt/kayobe/images/overcloud-{{ os_distribution }}-{{ os_release }}/overcloud-{{ os_distribution }}-{{ os_release }}.qcow2" + image_name: "overcloud-{{ os_distribution }}-{{ os_release }}" + tasks: + - block: + - name: Write out clouds.yaml + copy: + content: "{{ lookup('ansible.builtin.env', 'CLOUDS_YAML') }}" + dest: clouds.yaml + mode: 0600 + + - name: Write out secure.yaml + no_log: true + vars: + os_secrets: + clouds: + openstack: + auth: + application_credential_id: "{{ lookup('ansible.builtin.env', 'OS_APPLICATION_CREDENTIAL_ID') }}" + application_credential_secret: "{{ lookup('ansible.builtin.env', 'OS_APPLICATION_CREDENTIAL_SECRET') }}" + copy: + content: "{{ os_secrets | to_nice_yaml }}" + dest: secure.yaml + mode: 0600 + + - name: Ensure dependencies are installed + pip: + name: openstacksdk + + - name: Upload an image to Glance + openstack.cloud.image: + cloud: openstack + name: "{{ image_name }}" + container_format: bare + disk_format: qcow2 + state: present + filename: "{{ local_image_path }}" + + always: + - name: Remove clouds.yaml + file: + path: clouds.yaml + state: absent + + - name: Remove secure.yaml + file: + path: secure.yaml + state: absent diff --git a/etc/kayobe/ansible/pulp-host-image-upload.yml b/etc/kayobe/ansible/pulp-host-image-upload.yml index a06897d90..d3a44f133 100644 --- a/etc/kayobe/ansible/pulp-host-image-upload.yml +++ b/etc/kayobe/ansible/pulp-host-image-upload.yml @@ -1,12 +1,12 @@ --- - name: Upload and create a distribution for an image - hosts: localhost + hosts: seed vars: remote_pulp_url: "{{ stackhpc_release_pulp_url }}" remote_pulp_username: "{{ stackhpc_image_repository_username }}" remote_pulp_password: "{{ stackhpc_image_repository_password }}" repository_name: "kayobe-images-{{ openstack_release }}-{{ os_distribution }}-{{ os_release }}" - base_path: "kayobe-images/{{ openstack_release }}/{{ os_distribution }}/{{ os_release }}" + pulp_base_path: "kayobe-images/{{ openstack_release }}/{{ os_distribution }}/{{ os_release }}" tasks: - name: Print image tag debug: @@ -74,7 +74,7 @@ username: "{{ remote_pulp_username }}" password: "{{ remote_pulp_password }}" name: "{{ repository_name }}_latest" - base_path: "{{ base_path }}/latest" + base_path: "{{ pulp_base_path }}/latest" publication: "{{ publication_details.publication.pulp_href }}" content_guard: development state: present @@ -86,7 +86,7 @@ username: "{{ remote_pulp_username }}" password: "{{ remote_pulp_password }}" name: "{{ repository_name }}_{{ host_image_tag }}" - base_path: "{{ base_path }}/{{ host_image_tag }}" + base_path: "{{ pulp_base_path }}/{{ host_image_tag }}" publication: "{{ publication_details.publication.pulp_href }}" content_guard: development state: present @@ -95,26 +95,26 @@ - name: Update new images file with versioned path lineinfile: path: /tmp/updated_images.txt - line: "{{ remote_pulp_url }}/pulp/content/{{ base_path }}/\ + line: "{{ remote_pulp_url }}/pulp/content/{{ pulp_base_path }}/\ {{ host_image_tag }}/{{ found_files.files[0].path | basename }}" create: true - name: Update new images file with latest path lineinfile: path: /tmp/updated_images.txt - line: "{{ remote_pulp_url }}/pulp/content/{{ base_path }}/\ + line: "{{ remote_pulp_url }}/pulp/content/{{ pulp_base_path }}/\ latest/{{ found_files.files[0].path | basename }}" when: latest_distribution_details.changed - name: Print versioned path debug: - msg: "New versioned path: {{ remote_pulp_url }}/pulp/content/{{ base_path }}/\ + msg: "New versioned path: {{ remote_pulp_url }}/pulp/content/{{ pulp_base_path }}/\ {{ host_image_tag }}/{{ found_files.files[0].path | basename }}" when: latest_distribution_details.changed - name: Print latest path debug: - msg: "New latest path: {{ remote_pulp_url }}/pulp/content/{{ base_path }}/\ + msg: "New latest path: {{ remote_pulp_url }}/pulp/content/{{ pulp_base_path }}/\ latest/{{ found_files.files[0].path | basename }}" when: latest_distribution_details.changed diff --git a/etc/kayobe/environments/ci-builder/inventory/hosts b/etc/kayobe/environments/ci-builder/inventory/hosts index 49b7be166..33fda8b73 100644 --- a/etc/kayobe/environments/ci-builder/inventory/hosts +++ b/etc/kayobe/environments/ci-builder/inventory/hosts @@ -1,3 +1,3 @@ # A 'seed' host used for building images. [seed] -localhost ansible_connection=local ansible_python_interpreter=/usr/bin/python3 +builder diff --git a/etc/kayobe/overcloud-dib.yml b/etc/kayobe/overcloud-dib.yml index 8f59d58ef..d7f6dbd69 100644 --- a/etc/kayobe/overcloud-dib.yml +++ b/etc/kayobe/overcloud-dib.yml @@ -71,7 +71,7 @@ overcloud_dib_host_packages_extra: overcloud_dib_git_elements_extra: - repo: "https://github.com/stackhpc/stackhpc-image-elements" local: "{{ source_checkout_path }}/stackhpc-image-elements" - version: "v1.6.0" + version: "v1.6.1" elements_path: "elements" # List of git repositories containing Diskimage Builder (DIB) elements. See From c1a31acc9d8e85330b664f220051a39e1c2cb9ed Mon Sep 17 00:00:00 2001 From: Alex-Welsh Date: Tue, 2 Apr 2024 14:56:45 +0100 Subject: [PATCH 105/128] Fix AIO connectivity loss in automated script --- etc/kayobe/environments/ci-aio/automated-setup.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/etc/kayobe/environments/ci-aio/automated-setup.sh b/etc/kayobe/environments/ci-aio/automated-setup.sh index 5129db015..f5468a09a 100644 --- a/etc/kayobe/environments/ci-aio/automated-setup.sh +++ b/etc/kayobe/environments/ci-aio/automated-setup.sh @@ -84,6 +84,10 @@ kayobe overcloud host configure kayobe overcloud service deploy +if type apt; then + sudo cp /run/systemd/network/* /etc/systemd/network +fi + export KAYOBE_CONFIG_SOURCE_PATH=$BASE_PATH/src/kayobe-config export KAYOBE_VENV_PATH=$BASE_PATH/venvs/kayobe pushd $BASE_PATH/src/kayobe From faaabbb5aa92f547f48f513233fdf24785c9e7b7 Mon Sep 17 00:00:00 2001 From: Alex-Welsh Date: Thu, 4 Apr 2024 11:43:40 +0100 Subject: [PATCH 106/128] Fix AIO deploy script --- etc/kayobe/environments/ci-aio/automated-setup.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/etc/kayobe/environments/ci-aio/automated-setup.sh b/etc/kayobe/environments/ci-aio/automated-setup.sh index f5468a09a..84b9b5f09 100644 --- a/etc/kayobe/environments/ci-aio/automated-setup.sh +++ b/etc/kayobe/environments/ci-aio/automated-setup.sh @@ -72,6 +72,10 @@ fi sudo ip l set dummy1 up sudo ip l set dummy1 master breth1 +if type apt; then + sudo cp /run/systemd/network/* /etc/systemd/network +fi + export KAYOBE_VAULT_PASSWORD=$(cat $BASE_PATH/vault-pw) pushd $BASE_PATH/src/kayobe-config source kayobe-env --environment ci-aio @@ -84,10 +88,6 @@ kayobe overcloud host configure kayobe overcloud service deploy -if type apt; then - sudo cp /run/systemd/network/* /etc/systemd/network -fi - export KAYOBE_CONFIG_SOURCE_PATH=$BASE_PATH/src/kayobe-config export KAYOBE_VENV_PATH=$BASE_PATH/venvs/kayobe pushd $BASE_PATH/src/kayobe From 2d8d500925fd90b7cc83b4fd8af3f53329a2696e Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Mon, 8 Apr 2024 11:24:55 +0100 Subject: [PATCH 107/128] ci-multinode: Use skc-ci-aio user for ci-multinode env Similar to c338dd9b7cad77c14eb15eb0193d02b0c9ff78b4, but applied to ci-multinode instead of ci-aio. This user only has read-only access to the package and container repositories, so is safer than using the release-train-ci user which has read/write permissions. --- .../environments/ci-multinode/stackhpc-ci.yml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/etc/kayobe/environments/ci-multinode/stackhpc-ci.yml b/etc/kayobe/environments/ci-multinode/stackhpc-ci.yml index cdb6eb810..ae5768bac 100644 --- a/etc/kayobe/environments/ci-multinode/stackhpc-ci.yml +++ b/etc/kayobe/environments/ci-multinode/stackhpc-ci.yml @@ -11,6 +11,14 @@ kolla_docker_namespace: stackhpc-dev # Host and port of a package repository mirror. # Build and deploy the development Pulp service repositories. stackhpc_repo_mirror_url: "http://pulp-server.internal.sms-cloud:8080" +stackhpc_repo_mirror_username: "skc-ci-aio" +stackhpc_repo_mirror_password: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 36373536303261313239613761653261663437356566343865383563346334396136653666383765 + 6634396534653865633936653038383132396532386665370a366562383166353966663838316266 + 65333133636330623936623438666632316238376264313234346333346461623765633163353635 + 6565326136313564320a303231383438333062643533333335663034613439393665656162626137 + 65356232656164663831316530333136336362393636656566353635306565626636 # Build and deploy released Pulp repository versions. stackhpc_repo_centos_stream_baseos_version: "{{ stackhpc_pulp_repo_centos_stream_8_baseos_version }}" @@ -66,12 +74,5 @@ stackhpc_include_os_minor_version_in_repo_url: true # Push built images to the development Pulp service registry. stackhpc_docker_registry: "{{ stackhpc_repo_mirror_url | regex_replace('^https?://', '') }}" -# Username and password of container registry. -stackhpc_docker_registry_username: "stackhpc-kayobe-ci" -stackhpc_docker_registry_password: !vault | - $ANSIBLE_VAULT;1.1;AES256 - 33356166343730633865363431306535613736663764373034396132356131343066636530393534 - 3262646436663034633131316438633230383330633533350a386365313239303464383636376338 - 61656662333939333063343131633963636431663136643137636664633233633133396339613861 - 3038613063626138610a333566393937643630366564653163613364323965396130613433316537 - 39653335393831633362343934363866346262613166393561666336623062393935 +stackhpc_docker_registry_username: "{{ stackhpc_repo_mirror_username }}" +stackhpc_docker_registry_password: "{{ stackhpc_repo_mirror_password }}" From d77fcb1f64abbe50171d493df05e76e7f03e2a2e Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Mon, 8 Apr 2024 15:29:00 +0100 Subject: [PATCH 108/128] ci-multinode: Use Ark package repositories to install packages Similar to e9130b9c51161fdadd676932eae5f2c13f5948a8 but applied to ci-multinode rather than ci-aio. Previously we were using Test Pulp on SMS lab, but this is out of action. Switching to Ark allows CI jobs to run on Leafcloud (or anywhere with Internet access). --- etc/kayobe/environments/ci-multinode/stackhpc-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/etc/kayobe/environments/ci-multinode/stackhpc-ci.yml b/etc/kayobe/environments/ci-multinode/stackhpc-ci.yml index ae5768bac..32f8775e1 100644 --- a/etc/kayobe/environments/ci-multinode/stackhpc-ci.yml +++ b/etc/kayobe/environments/ci-multinode/stackhpc-ci.yml @@ -10,7 +10,8 @@ kolla_docker_namespace: stackhpc-dev # Host and port of a package repository mirror. # Build and deploy the development Pulp service repositories. -stackhpc_repo_mirror_url: "http://pulp-server.internal.sms-cloud:8080" +# Use Ark's package repositories to install packages. +stackhpc_repo_mirror_url: "{{ stackhpc_release_pulp_url }}" stackhpc_repo_mirror_username: "skc-ci-aio" stackhpc_repo_mirror_password: !vault | $ANSIBLE_VAULT;1.1;AES256 From c57f2c3a7b93d17ed1ccfd31c8c596dd6c2e3064 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Mon, 8 Apr 2024 15:22:07 +0100 Subject: [PATCH 109/128] ci-multinode: Allow rebooting for SELinux state The Yoga overcloud host images currently have SELinux disabled, but the default config enables SELinux in permissive mode on Rocky Linux 9. This change allows the ci-multinode environment to run on these images. --- etc/kayobe/environments/ci-multinode/globals.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/kayobe/environments/ci-multinode/globals.yml b/etc/kayobe/environments/ci-multinode/globals.yml index daecef4f2..fe7285f4c 100644 --- a/etc/kayobe/environments/ci-multinode/globals.yml +++ b/etc/kayobe/environments/ci-multinode/globals.yml @@ -64,7 +64,7 @@ stackhpc_barbican_role_id_file_path: "/tmp/barbican-role-id" ############################################################################### # Avoid a reboot. -disable_selinux_do_reboot: false +disable_selinux_do_reboot: true ############################################################################### # Dummy variable to allow Ansible to accept this file. From e2b2f40cd138a212b2801ed410f791003a0a5fd5 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Mon, 8 Apr 2024 15:31:37 +0100 Subject: [PATCH 110/128] ci-multinode: Add API FQDNs to /etc/hosts in fix-networking.yml This avoids using the add-fqdn.yml playbook in terraform-kayobe-multinode, which requires the Terraform/Ansible client to have access to all hosts. --- etc/kayobe/ansible/fix-networking.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/etc/kayobe/ansible/fix-networking.yml b/etc/kayobe/ansible/fix-networking.yml index 0b14f9ddf..8105db8f4 100644 --- a/etc/kayobe/ansible/fix-networking.yml +++ b/etc/kayobe/ansible/fix-networking.yml @@ -10,11 +10,13 @@ # Work around no known_hosts entry on first boot. ansible_ssh_common_args: "-o StrictHostKeyChecking=no" tasks: - - name: Ensure `hosts` file contains pulp entries + - name: Ensure `hosts` file contains pulp and API entries blockinfile: path: /etc/hosts - marker: "# {mark} Kayobe Pulp entries" + marker: "# {mark} Kayobe entries" block: | 10.0.0.34 pelican pelican.service.compute.sms-lab.cloud 10.205.3.187 pulp-server pulp-server.internal.sms-cloud + 192.168.37.2 internal.infra.mos.{{ root_domain }} + 192.168.39.2 public.infra.mos.{{ root_domain }} become: true From 2ca68f1713b5784fcfc577f37d3b6528f8017060 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Wed, 10 Apr 2024 09:13:17 +0100 Subject: [PATCH 111/128] ci-multinode: Wait for connection in fix-networking.yml This allows us to drop the fix-homedir-ownership.yml playbook in terraform-kayobe-multinode, which also performed the function of waiting for hosts to become reachable. --- etc/kayobe/ansible/fix-networking.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/etc/kayobe/ansible/fix-networking.yml b/etc/kayobe/ansible/fix-networking.yml index 8105db8f4..01a833264 100644 --- a/etc/kayobe/ansible/fix-networking.yml +++ b/etc/kayobe/ansible/fix-networking.yml @@ -10,6 +10,9 @@ # Work around no known_hosts entry on first boot. ansible_ssh_common_args: "-o StrictHostKeyChecking=no" tasks: + - name: Ensure hosts are reachable + ansible.builtin.wait_for_connection: + - name: Ensure `hosts` file contains pulp and API entries blockinfile: path: /etc/hosts From 33c0d38622136cde0fd46458f837ecd35b18ac40 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Wed, 10 Apr 2024 09:12:05 +0100 Subject: [PATCH 112/128] ci-multinode: Use qemu virtualisation Most multinode environments will use nested virtualisation, and we can't guarantee that nested KVM support is available. Use QEMU as a lowest common denominator. We might consider setting this dynamically based on the hypervisor in future. --- etc/kayobe/environments/ci-multinode/kolla/globals.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/etc/kayobe/environments/ci-multinode/kolla/globals.yml b/etc/kayobe/environments/ci-multinode/kolla/globals.yml index eab31a1d8..4f9506be0 100644 --- a/etc/kayobe/environments/ci-multinode/kolla/globals.yml +++ b/etc/kayobe/environments/ci-multinode/kolla/globals.yml @@ -1,4 +1,9 @@ --- +# Most development environments will use nested virtualisation, and we can't +# guarantee that nested KVM support is available. Use QEMU as a lowest common +# denominator. +nova_compute_virt_type: qemu + # Reduce the control plane's memory footprint by limiting the number of worker # processes to two per-service when running in a VM. openstack_service_workers: "{% raw %}{{ [ansible_facts.processor_vcpus, 2 if ansible_facts.virtualization_role == 'guest' else 5] | min }}{% endraw %}" From eb1f88ec51ee65a504e064b99e3aadc4ab65cbe0 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Mon, 8 Apr 2024 16:03:51 +0100 Subject: [PATCH 113/128] ci-multinode: Set default Ceph release to Quincy on Rocky Linux 9 Pacific is not supported on Rocky Linux 9, so it does not make sense as a default. --- etc/kayobe/environments/ci-multinode/cephadm.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/etc/kayobe/environments/ci-multinode/cephadm.yml b/etc/kayobe/environments/ci-multinode/cephadm.yml index 7885a5735..4a9d3f448 100644 --- a/etc/kayobe/environments/ci-multinode/cephadm.yml +++ b/etc/kayobe/environments/ci-multinode/cephadm.yml @@ -2,6 +2,12 @@ ############################################################################### # Cephadm deployment configuration. +# Ceph release name. +cephadm_ceph_release: "{{ 'quincy' if (ansible_facts['distribution_release'] == 'jammy' or ansible_facts.distribution_major_version == '9') else 'pacific' }}" + +# Ceph container image tag. +cephadm_image_tag: "{{ 'v17.2.7' if cephadm_ceph_release == 'quincy' else 'v16.2.14' }}" + # Ceph OSD specification. cephadm_osd_spec: service_type: osd From 50378160654a5ed5d74cb17180cb3999401d09b5 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Wed, 10 Apr 2024 09:09:23 +0100 Subject: [PATCH 114/128] os_capacity: Add tags to playbook, update vault docs Previously the first deployment of a system with a Vault CA for internal TLS and os_capacity enabled would fail when deploying HAProxy. os_capacity deployment requires admin-openrc.sh to exist, but because of the use of -kt haproxy the post-deploy tasks that create it will be skipped. This change fixes the issue by adding an os_capacity tag to the relevant plays, and updating the Vault docs to skip the new tag when deploying HAProxy. --- doc/source/configuration/vault.rst | 2 +- etc/kayobe/ansible/deploy-os-capacity-exporter.yml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/source/configuration/vault.rst b/doc/source/configuration/vault.rst index 4cb39b61b..21268f108 100644 --- a/doc/source/configuration/vault.rst +++ b/doc/source/configuration/vault.rst @@ -111,7 +111,7 @@ Setup HAProxy config for Vault .. code-block:: - kayobe overcloud service deploy -kt haproxy + kayobe overcloud service deploy --skip-tags os_capacity -kt haproxy Setup Vault HA on the overcloud hosts ------------------------------------- diff --git a/etc/kayobe/ansible/deploy-os-capacity-exporter.yml b/etc/kayobe/ansible/deploy-os-capacity-exporter.yml index 978c13e62..cc3afa7b0 100644 --- a/etc/kayobe/ansible/deploy-os-capacity-exporter.yml +++ b/etc/kayobe/ansible/deploy-os-capacity-exporter.yml @@ -1,6 +1,7 @@ --- - name: Remove legacy os_exporter.cfg file hosts: network + tags: os_capacity gather_facts: false tasks: - name: Ensure legacy os_exporter.cfg config file is deleted @@ -11,6 +12,7 @@ - name: Deploy os-capacity exporter hosts: monitoring + tags: os_capacity gather_facts: false tasks: - name: Create os-capacity directory From a6082d0fc3163b4f6ef8a83ba518d8b3722e5ac9 Mon Sep 17 00:00:00 2001 From: Scott Davidson <49713135+sd109@users.noreply.github.com> Date: Thu, 11 Apr 2024 12:18:17 +0100 Subject: [PATCH 115/128] Update Magnum driver from v0.12.0 to v0.13.0 --- etc/kayobe/kolla.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/kayobe/kolla.yml b/etc/kayobe/kolla.yml index 6db29a0cc..adf3081cf 100644 --- a/etc/kayobe/kolla.yml +++ b/etc/kayobe/kolla.yml @@ -328,7 +328,7 @@ kolla_build_blocks: magnum_base_footer: | RUN curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | head -n -1 | bash {% raw %} - {% set magnum_capi_packages = ['git+https://github.com/stackhpc/magnum-capi-helm.git@v0.12.0'] %} + {% set magnum_capi_packages = ['git+https://github.com/stackhpc/magnum-capi-helm.git@v0.13.0'] %} RUN {{ macros.install_pip(magnum_capi_packages | customizable("pip_packages")) }} {% endraw %} # Dict mapping image customization variable names to their values. From 55b343b63dc8fb07d053ec4d242f468e9cf05142 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Thu, 11 Apr 2024 13:54:09 +0100 Subject: [PATCH 116/128] Revert "docs: Add an upgrade doc note about Glance show_multiple_locations" This reverts commit 99838a89987a6c4250f4eb0a55ed7d199853b5fe. It applies to the Zed upgrade, not Antelope. --- doc/source/operations/upgrading.rst | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/doc/source/operations/upgrading.rst b/doc/source/operations/upgrading.rst index a23495bd6..89f8f6aa8 100644 --- a/doc/source/operations/upgrading.rst +++ b/doc/source/operations/upgrading.rst @@ -162,20 +162,6 @@ environment. This can result in significant changes to the Kolla config. Take extra care when creating the Antelope branch of the kayobe-config and always check the config diff. -Glance show_multiple_locations disabled ---------------------------------------- - -Kolla Ansible no longer sets ``show_multiple_locations = True`` in Glance by -default when Glance's Ceph RBD backend is enabled. This was applied as a fix -but operators must note that this, in turn, disables Cinder and Nova's -optimisations. In particular, this can increase instance creation times due to -a lack of copy-on-write. - -On the other hand, these optimisations might have been causing other trouble -for operators. Please see `LP#1992153 -`__. Operators relying -on this feature can set the flag themselves using service config overrides. - Known issues ============ From 494783864874509a8e3407c72fe528506bbb416c Mon Sep 17 00:00:00 2001 From: Scott Davidson <49713135+sd109@users.noreply.github.com> Date: Thu, 11 Apr 2024 15:43:01 +0100 Subject: [PATCH 117/128] Update Magnum image tags --- etc/kayobe/kolla-image-tags.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/etc/kayobe/kolla-image-tags.yml b/etc/kayobe/kolla-image-tags.yml index 9ff72b2b0..69165cf06 100644 --- a/etc/kayobe/kolla-image-tags.yml +++ b/etc/kayobe/kolla-image-tags.yml @@ -16,8 +16,8 @@ kolla_image_tags: rocky-9: 2023.1-rocky-9-20240205T162323 ubuntu-jammy: 2023.1-ubuntu-jammy-20240221T133905 magnum: - rocky-9: 2023.1-rocky-9-20240320T133822 - ubuntu-jammy: 2023.1-ubuntu-jammy-20240320T133822 + rocky-9: 2023.1-rocky-9-20240411T125311 + ubuntu-jammy: 2023.1-ubuntu-jammy-20240411T125311 neutron: rocky-9: 2023.1-rocky-9-20240202T145927 ubuntu-jammy: 2023.1-ubuntu-jammy-20240221T103817 From 39fa6433b4eee5a75beabd6b993913a53d1394d6 Mon Sep 17 00:00:00 2001 From: Alex-Welsh Date: Mon, 15 Apr 2024 10:57:31 +0100 Subject: [PATCH 118/128] Fix tox whitespace warning --- etc/kayobe/kolla.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/kayobe/kolla.yml b/etc/kayobe/kolla.yml index 110ee750f..b4dc649a5 100644 --- a/etc/kayobe/kolla.yml +++ b/etc/kayobe/kolla.yml @@ -369,7 +369,7 @@ kolla_build_customizations: "{{ kolla_build_customizations_common | combine(koll # Dict mapping Kolla Dockerfile ARG names to their values. kolla_build_args: - node_exporter_version: "1.5.0" # kolla has 1.4.0 + node_exporter_version: "1.5.0" # kolla has 1.4.0 node_exporter_sha256sum: "af999fd31ab54ed3a34b9f0b10c28e9acee9ef5ac5a5d5edfdde85437db7acbb" ############################################################################### From f2523ece958ff01dea6f235d57af450008249d0d Mon Sep 17 00:00:00 2001 From: sd109 Date: Mon, 15 Apr 2024 13:29:41 +0100 Subject: [PATCH 119/128] Add release note --- .../notes/bump-magnum-capi-helm-d766b5956de65d31.yaml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 releasenotes/notes/bump-magnum-capi-helm-d766b5956de65d31.yaml diff --git a/releasenotes/notes/bump-magnum-capi-helm-d766b5956de65d31.yaml b/releasenotes/notes/bump-magnum-capi-helm-d766b5956de65d31.yaml new file mode 100644 index 000000000..eb1e37640 --- /dev/null +++ b/releasenotes/notes/bump-magnum-capi-helm-d766b5956de65d31.yaml @@ -0,0 +1,4 @@ +--- + features: + - | + Updates Magnum CAPI Helm driver version to v0.13.0 From c2bd71ba51c4f73ce83e1f0bd12dba7c6060cca6 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Thu, 16 Nov 2023 11:42:17 +0000 Subject: [PATCH 120/128] Add retries to overcloud host image pulp tasks Retries have been added to the stackhpc.pulp collection to improve reliability. Adding the same here. --- .../ansible/pulp-host-image-promote.yml | 7 +++++ etc/kayobe/ansible/pulp-host-image-upload.yml | 26 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/etc/kayobe/ansible/pulp-host-image-promote.yml b/etc/kayobe/ansible/pulp-host-image-promote.yml index d93d71d51..42f98b423 100644 --- a/etc/kayobe/ansible/pulp-host-image-promote.yml +++ b/etc/kayobe/ansible/pulp-host-image-promote.yml @@ -19,6 +19,9 @@ name: "{{ repository_name }}_{{ promotion_tag }}" base_path: "{{ base_path }}/{{ promotion_tag }}" register: distribution_details + until: distribution_details is success + retries: 3 + delay: 5 - name: Fail if the image does not exist fail: @@ -34,6 +37,10 @@ base_path: "{{ base_path }}/{{ promotion_tag }}" content_guard: release state: present + register: content_guard_result + until: content_guard_result is success + retries: 3 + delay: 5 - name: Print version tag and os debug: diff --git a/etc/kayobe/ansible/pulp-host-image-upload.yml b/etc/kayobe/ansible/pulp-host-image-upload.yml index d3a44f133..cc4876080 100644 --- a/etc/kayobe/ansible/pulp-host-image-upload.yml +++ b/etc/kayobe/ansible/pulp-host-image-upload.yml @@ -25,6 +25,10 @@ password: "{{ remote_pulp_password }}" file: "{{ found_files.files[0].path }}" state: present + register: upload_result + until: upload_result is success + retries: 3 + delay: 60 - name: Get sha256 hash ansible.builtin.stat: @@ -40,6 +44,10 @@ sha256: "{{ file_stats.stat.checksum }}" relative_path: "{{ found_files.files[0].path | basename }}" state: present + register: file_content_result + until: file_content_result is success + retries: 3 + delay: 5 - name: Ensure file repo exists pulp.squeezer.file_repository: @@ -48,6 +56,10 @@ password: "{{ remote_pulp_password }}" name: "{{ repository_name }}" state: present + register: file_repo_result + until: file_repo_result is success + retries: 3 + delay: 5 - name: Add content to file repo pulp.squeezer.file_repository_content: @@ -58,6 +70,10 @@ present_content: - relative_path: "{{ found_files.files[0].path | basename }}" sha256: "{{ file_stats.stat.checksum }}" + register: file_repo_content_result + until: file_repo_content_result is success + retries: 3 + delay: 5 - name: Create a new publication to point to this version pulp.squeezer.file_publication: @@ -67,6 +83,9 @@ repository: "{{ repository_name }}" state: present register: publication_details + until: publication_details is success + retries: 3 + delay: 5 - name: Update distribution for latest version pulp.squeezer.file_distribution: @@ -79,6 +98,9 @@ content_guard: development state: present register: latest_distribution_details + until: latest_distribution_details is success + retries: 3 + delay: 5 - name: Create distribution for given version pulp.squeezer.file_distribution: @@ -91,6 +113,10 @@ content_guard: development state: present when: latest_distribution_details.changed + register: distribution_result + until: distribution_result is success + retries: 3 + delay: 5 - name: Update new images file with versioned path lineinfile: From 680bb722e0c3307de45645e45054393102fb8afb Mon Sep 17 00:00:00 2001 From: Seunghun Lee Date: Mon, 15 Apr 2024 12:00:18 +0100 Subject: [PATCH 121/128] Update Ubuntu horizon tag to fix CVE-2023-31122 --- etc/kayobe/kolla-image-tags.yml | 2 ++ releasenotes/notes/bump-horizon-694d426decbf7df3.yaml | 5 +++++ 2 files changed, 7 insertions(+) create mode 100644 releasenotes/notes/bump-horizon-694d426decbf7df3.yaml diff --git a/etc/kayobe/kolla-image-tags.yml b/etc/kayobe/kolla-image-tags.yml index 9ff72b2b0..9eeba0c83 100644 --- a/etc/kayobe/kolla-image-tags.yml +++ b/etc/kayobe/kolla-image-tags.yml @@ -12,6 +12,8 @@ kolla_image_tags: heat: rocky-9: 2023.1-rocky-9-20240319T134201 ubuntu-jammy: 2023.1-ubuntu-jammy-20240319T134201 + horizon: + ubuntu-jammy: 2023.1-ubuntu-jammy-20240402T104530 letsencrypt: rocky-9: 2023.1-rocky-9-20240205T162323 ubuntu-jammy: 2023.1-ubuntu-jammy-20240221T133905 diff --git a/releasenotes/notes/bump-horizon-694d426decbf7df3.yaml b/releasenotes/notes/bump-horizon-694d426decbf7df3.yaml new file mode 100644 index 000000000..780797d9e --- /dev/null +++ b/releasenotes/notes/bump-horizon-694d426decbf7df3.yaml @@ -0,0 +1,5 @@ +--- +security: + - | + Update Horizon on Ubuntu to include apache2 package ``2.4.52-1ubuntu4.8`` + which fixes CVE-2023-31122. From c2edb3825a467cbad6e9713c17ec67f185b286bd Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Tue, 16 Apr 2024 13:37:55 +0200 Subject: [PATCH 122/128] Raise alert on degraded network bonds This will raise a alert when at least one of the bond members is down. Adapted from awesome-prometheus-alerts [1]. [1] https://samber.github.io/awesome-prometheus-alerts/rules.html#rule-host-and-hardware-1-34 --- etc/kayobe/kolla/config/prometheus/system.rules | 9 +++++++++ .../network-bond-degraded-alert-d2a0b05002609ac1.yaml | 5 +++++ 2 files changed, 14 insertions(+) create mode 100644 releasenotes/notes/network-bond-degraded-alert-d2a0b05002609ac1.yaml diff --git a/etc/kayobe/kolla/config/prometheus/system.rules b/etc/kayobe/kolla/config/prometheus/system.rules index c82bed16e..6ee3eed3c 100644 --- a/etc/kayobe/kolla/config/prometheus/system.rules +++ b/etc/kayobe/kolla/config/prometheus/system.rules @@ -96,6 +96,15 @@ groups: summary: Host clock not synchronising (instance {{ $labels.instance }}) description: "Clock not synchronising. Ensure NTP is configured on this host." + - alert: HostNetworkBondDegraded + expr: (node_bonding_active - node_bonding_slaves) != 0 + for: 2m + labels: + severity: warning + annotations: + summary: Host network bond degraded (instance {{ $labels.instance }}) + description: "Bond {{ $labels.master }} degraded on {{ $labels.instance }}" + - alert: HostConntrackLimit expr: node_nf_conntrack_entries / node_nf_conntrack_entries_limit > 0.8 for: 5m diff --git a/releasenotes/notes/network-bond-degraded-alert-d2a0b05002609ac1.yaml b/releasenotes/notes/network-bond-degraded-alert-d2a0b05002609ac1.yaml new file mode 100644 index 000000000..c987c7959 --- /dev/null +++ b/releasenotes/notes/network-bond-degraded-alert-d2a0b05002609ac1.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Adds a new Prometheus alert ``HostNetworkBondDegraded`` which will be + raised when at least one bond member is down. From b19b42ed1f52bc0d3ea8c30c88972cd8ce23f4ef Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Thu, 18 Apr 2024 10:52:39 +0100 Subject: [PATCH 123/128] docs: Remove prometheus and grafana config symlinks These are no longer necessary due to support for kayobe multiple environment merging being backported to Antelope. --- doc/source/configuration/monitoring.rst | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/doc/source/configuration/monitoring.rst b/doc/source/configuration/monitoring.rst index 822a3c02f..069bf4700 100644 --- a/doc/source/configuration/monitoring.rst +++ b/doc/source/configuration/monitoring.rst @@ -42,17 +42,6 @@ The configuration options can be found in .. literalinclude:: ../../../etc/kayobe/stackhpc-monitoring.yml :language: yaml -In order to enable stock monitoring configuration within a particular -environment, create the following symbolic links: - -.. code-block:: console - - cd $KAYOBE_CONFIG_PATH - ln -s ../../../../kolla/config/grafana/ environments/$KAYOBE_ENVIRONMENT/kolla/config/ - ln -s ../../../../kolla/config/prometheus/ environments/$KAYOBE_ENVIRONMENT/kolla/config/ - -and commit them to the config repository. - SMART Drive Monitoring ====================== From e0f3aca6d0f77edec5e9416accc712ae38e43169 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Mon, 22 Apr 2024 11:48:27 +0100 Subject: [PATCH 124/128] docs: Add more context and links to vault docs --- doc/source/configuration/vault.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/doc/source/configuration/vault.rst b/doc/source/configuration/vault.rst index d598a63a5..a1a8429e2 100644 --- a/doc/source/configuration/vault.rst +++ b/doc/source/configuration/vault.rst @@ -6,6 +6,18 @@ This document describes how to deploy Hashicorp Vault for internal PKI purposes using the `StackHPC Hashicorp collection `_ +Vault may be used as a Certificate Authority to generate certificates for: + +* OpenStack internal API +* OpenStack backend APIs +* RabbitMQ + +TLS support is described in the :kolla-ansible-doc:`Kolla Ansible documentation +` and the :kayobe-doc:`Kayobe documentation +`. + +Vault may also be used as the secret store for Barbican. + Background ========== From 0243ad874ec6ab842f96aa53370f127d3a079f5d Mon Sep 17 00:00:00 2001 From: Jakub Darmach Date: Mon, 22 Apr 2024 17:54:19 +0200 Subject: [PATCH 125/128] Magnum - removed appending to ca.cart Appending to ca.crt in make-cert-client.sh (introduced in #724203) causes multiple identical ca certs being added into /etc/kubernetes/certs/ca.crt which prevents kube-controller-manager from starting --- etc/kayobe/kolla-image-tags.yml | 4 ++-- .../notes/magnum-remove-cert-append-8797b640f25644ea.yaml | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/magnum-remove-cert-append-8797b640f25644ea.yaml diff --git a/etc/kayobe/kolla-image-tags.yml b/etc/kayobe/kolla-image-tags.yml index 535827ef5..df3b5f4b6 100644 --- a/etc/kayobe/kolla-image-tags.yml +++ b/etc/kayobe/kolla-image-tags.yml @@ -18,8 +18,8 @@ kolla_image_tags: rocky-9: 2023.1-rocky-9-20240205T162323 ubuntu-jammy: 2023.1-ubuntu-jammy-20240221T133905 magnum: - rocky-9: 2023.1-rocky-9-20240411T125311 - ubuntu-jammy: 2023.1-ubuntu-jammy-20240411T125311 + rocky-9: 2023.1-rocky-9-20240422T152338 + ubuntu-jammy: 2023.1-ubuntu-jammy-20240422T152338 neutron: rocky-9: 2023.1-rocky-9-20240202T145927 ubuntu-jammy: 2023.1-ubuntu-jammy-20240221T103817 diff --git a/releasenotes/notes/magnum-remove-cert-append-8797b640f25644ea.yaml b/releasenotes/notes/magnum-remove-cert-append-8797b640f25644ea.yaml new file mode 100644 index 000000000..20d195596 --- /dev/null +++ b/releasenotes/notes/magnum-remove-cert-append-8797b640f25644ea.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fixes appending to ca.crt in make-cert-client.sh causing multiple identical + ca certs being added into /etc/kubernetes/certs/ca.crt. From 6cf594d304cd1936d99a5c0ada70d4cd6861a4b1 Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Tue, 23 Apr 2024 11:07:18 +0200 Subject: [PATCH 126/128] Add alert to detect bonds with a single link This change adds a new Prometheus alert HostNetworkBondSingleLink which will be raised when a bond is configured with only one member. This can happen when NetworkManager detects that a bond member is down at boot time. This would fail to be detected by the HostNetworkBondDegraded alert. --- etc/kayobe/kolla/config/prometheus/system.rules | 15 +++++++++++++++ etc/kayobe/stackhpc-monitoring.yml | 4 ++++ ...network-bond-single-link-766adf41a3c2fd4e.yaml | 8 ++++++++ 3 files changed, 27 insertions(+) create mode 100644 releasenotes/notes/network-bond-single-link-766adf41a3c2fd4e.yaml diff --git a/etc/kayobe/kolla/config/prometheus/system.rules b/etc/kayobe/kolla/config/prometheus/system.rules index 6ee3eed3c..613368be6 100644 --- a/etc/kayobe/kolla/config/prometheus/system.rules +++ b/etc/kayobe/kolla/config/prometheus/system.rules @@ -104,7 +104,22 @@ groups: annotations: summary: Host network bond degraded (instance {{ $labels.instance }}) description: "Bond {{ $labels.master }} degraded on {{ $labels.instance }}" +{% endraw %} +{% if alertmanager_warn_network_bond_single_link | bool %} +{% raw %} + - alert: HostNetworkBondSingleLink + expr: node_bonding_slaves == 1 + for: 2m + labels: + severity: warning + annotations: + summary: Host network bond with a single link (instance {{ $labels.instance }}) + description: "Bond {{ $labels.master }} configured with a single link on {{ $labels.instance }}" +{% endraw %} +{% endif %} + +{% raw %} - alert: HostConntrackLimit expr: node_nf_conntrack_entries / node_nf_conntrack_entries_limit > 0.8 for: 5m diff --git a/etc/kayobe/stackhpc-monitoring.yml b/etc/kayobe/stackhpc-monitoring.yml index f08e552c3..e8e0bb91f 100644 --- a/etc/kayobe/stackhpc-monitoring.yml +++ b/etc/kayobe/stackhpc-monitoring.yml @@ -8,6 +8,10 @@ # of free memory is lower than this value an alert will be triggered. alertmanager_low_memory_threshold_gib: 5 +# Whether to raise an alert if any network bond is configured with a single +# link. Change to false to disable this alert. +alertmanager_warn_network_bond_single_link: true + ############################################################################### # Exporter configuration diff --git a/releasenotes/notes/network-bond-single-link-766adf41a3c2fd4e.yaml b/releasenotes/notes/network-bond-single-link-766adf41a3c2fd4e.yaml new file mode 100644 index 000000000..66d66f40b --- /dev/null +++ b/releasenotes/notes/network-bond-single-link-766adf41a3c2fd4e.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Adds a new Prometheus alert ``HostNetworkBondSingleLink`` which will be + raised when a bond is configured with only one member. This can happen when + NetworkManager detects that a bond member is down at boot time. This alert + can be disabled by setting ``alertmanager_warn_network_bond_single_link`` + to ``false``. From 084e47ac493514955b1ab582e6acf32e859bbaeb Mon Sep 17 00:00:00 2001 From: Bartosz Bezak Date: Tue, 26 Mar 2024 13:44:16 +0100 Subject: [PATCH 127/128] add playbook with workaround for 'tc mirred to Houston' --- etc/kayobe/ansible/fix-houston.yml | 44 +++++++++++++++++++ .../fix-houston-interface.service.j2 | 20 +++++++++ ...ix-houston-tc-mirred-bfb16c89f63b472a.yaml | 12 +++++ 3 files changed, 76 insertions(+) create mode 100644 etc/kayobe/ansible/fix-houston.yml create mode 100644 etc/kayobe/ansible/templates/fix-houston-interface.service.j2 create mode 100644 releasenotes/notes/fix-houston-tc-mirred-bfb16c89f63b472a.yaml diff --git a/etc/kayobe/ansible/fix-houston.yml b/etc/kayobe/ansible/fix-houston.yml new file mode 100644 index 000000000..6fa865792 --- /dev/null +++ b/etc/kayobe/ansible/fix-houston.yml @@ -0,0 +1,44 @@ +--- +# When OVS HW offloading is enabled - typically in conjunction with VF-LAG and ASAP^2 +# the DMESG log reports frequent errors on the internal OVS Bridge interface: +# "tc mirred to Houston: device bond0-ovs is down". +# This interface is down by default. The errors are mitigated by bringing the interface up. +# For further context, see: +# https://bugs.launchpad.net/charm-neutron-openvswitch/+bug/1899364 +# https://patchwork.kernel.org/project/netdevbpf/patch/c2ef23da1d9a4eb62f4e7b7c4540f9bafb553c15.1658420239.git.dcaratti@redhat.com/ +# To deploy this playbook, use the following commands: +# kayobe playbook run $KAYOBE_CONFIG_PATH/ansible/fix-houston.yml +# Enable with Kayobe Hooks by running: +# mkdir -p ${KAYOBE_CONFIG_PATH}/hooks/overcloud-service-deploy/post.d +# cd ${KAYOBE_CONFIG_PATH}/hooks/overcloud-service-deploy/post.d +# ln -s ../../../ansible/fix-houston.yml 90-fix-houston.yml + +- name: Create Systemd Unit to workaround 'tc mirred to Houston' error + hosts: network,compute + become: yes + + tasks: + - name: Include kolla-ansible host vars + include_vars: "{{ kolla_config_path }}/inventory/overcloud/host_vars/{{ inventory_hostname }}" + + - name: Create systemd service for -ovs network interface + template: + src: fix-houston-interface.service.j2 + dest: "/etc/systemd/system/fix-houston-{{ item }}.service" + loop: "{{ neutron_bridge_name.split(',') }}" + vars: + interface_name: "{{ item }}" + when: neutron_bridge_name | length > 0 + notify: reload systemd + + - name: Enable and start systemd service for -ovs network interface + systemd: + name: "fix-houston-{{ item }}" + enabled: yes + state: started + when: neutron_bridge_name | length > 0 + loop: "{{ neutron_bridge_name.split(',') }}" + + handlers: + - name: reload systemd + command: systemctl daemon-reload diff --git a/etc/kayobe/ansible/templates/fix-houston-interface.service.j2 b/etc/kayobe/ansible/templates/fix-houston-interface.service.j2 new file mode 100644 index 000000000..24696b13e --- /dev/null +++ b/etc/kayobe/ansible/templates/fix-houston-interface.service.j2 @@ -0,0 +1,20 @@ +[Unit] +# This service addresses a specific issue when OVS HW offloading is enabled +# typically in conjunction with VF-LAG and ASAP^2 +# the DMESG log reports frequent errors on the internal OVS Bridge interface: +# "tc mirred to Houston: device bond0-ovs is down". +# This interface is down by default. The errors are mitigated by bringing the interface up. +# For further context, see: +# https://bugs.launchpad.net/charm-neutron-openvswitch/+bug/1899364 +# https://patchwork.kernel.org/project/netdevbpf/patch/c2ef23da1d9a4eb62f4e7b7c4540f9bafb553c15.1658420239.git.dcaratti@redhat.com/ +Description=Bring up {{ interface_name }} interface +After=kolla-openvswitch_vswitchd-container.service + +[Service] +Type=oneshot +ExecStartPre=/usr/bin/timeout 60s /bin/bash -c 'until ip link show {{ interface_name }}; do sleep 1; done' +ExecStart=/sbin/ip link set {{ interface_name }} up +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target diff --git a/releasenotes/notes/fix-houston-tc-mirred-bfb16c89f63b472a.yaml b/releasenotes/notes/fix-houston-tc-mirred-bfb16c89f63b472a.yaml new file mode 100644 index 000000000..64c619cee --- /dev/null +++ b/releasenotes/notes/fix-houston-tc-mirred-bfb16c89f63b472a.yaml @@ -0,0 +1,12 @@ +--- +fixes: + - | + Adds a custom ``fix-houston.yml`` playbook to address dmesg errors, specifically: + "tc mirred to Houston: device bond0-ovs is down". This error typically appears + when OVS HW offloading is enabled, often in conjunction with VF-LAG and ASAP^2. + Detailed usage instructions are provided within the playbook's comments. + Additional context is available at the following links: + `LP#1899364 + `__ + `Kernel Patch + `__ From 2d8c15b78f723c4874c304acbdca5b2d809c409e Mon Sep 17 00:00:00 2001 From: Matt Crees Date: Thu, 25 Apr 2024 10:01:22 +0100 Subject: [PATCH 128/128] Correct backup for seed images in RL9 migration Current instructions have a recursive copy: ``cp: cannot copy a directory, '/var/lib/libvirt/images', into itself, '/var/lib/libvirt/images/backup/images'`` --- doc/source/operations/rocky-linux-9.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/operations/rocky-linux-9.rst b/doc/source/operations/rocky-linux-9.rst index 22b9323ae..99c827a87 100644 --- a/doc/source/operations/rocky-linux-9.rst +++ b/doc/source/operations/rocky-linux-9.rst @@ -745,8 +745,8 @@ Full procedure .. code:: console - sudo mkdir /var/lib/libvirt/images/backup - sudo cp -r /var/lib/libvirt/images /var/lib/libvirt/images/backup + sudo mkdir /var/lib/libvirt/images-backup + sudo cp -r /var/lib/libvirt/images /var/lib/libvirt/images-backup 9. Delete the seed root volume (check the structure & naming conventions first)