python-watcher 15.1.0__py3-none-any.whl → 15.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_watcher-15.1.0.dist-info → python_watcher-15.1.1.dist-info}/METADATA +1 -1
- {python_watcher-15.1.0.dist-info → python_watcher-15.1.1.dist-info}/RECORD +25 -25
- {python_watcher-15.1.0.dist-info → python_watcher-15.1.1.dist-info}/WHEEL +1 -1
- python_watcher-15.1.1.dist-info/pbr.json +1 -0
- watcher/common/cinder_helper.py +58 -16
- watcher/common/service.py +2 -2
- watcher/conf/grafana_client.py +1 -0
- watcher/conf/maas_client.py +1 -0
- watcher/decision_engine/model/element/base.py +4 -1
- watcher/decision_engine/model/model_root.py +13 -2
- watcher/decision_engine/strategy/strategies/zone_migration.py +34 -11
- watcher/tests/api/v1/test_audits.py +33 -0
- watcher/tests/common/test_cinder_helper.py +206 -16
- watcher/tests/common/test_service.py +22 -0
- watcher/tests/decision_engine/model/test_model.py +39 -9
- watcher/tests/decision_engine/strategy/strategies/test_zone_migration.py +109 -0
- python_watcher-15.1.0.dist-info/pbr.json +0 -1
- {python_watcher-15.1.0.data → python_watcher-15.1.1.data}/data/etc/apache2/watcher +0 -0
- {python_watcher-15.1.0.data → python_watcher-15.1.1.data}/data/etc/watcher/README-watcher.conf.txt +0 -0
- {python_watcher-15.1.0.data → python_watcher-15.1.1.data}/data/etc/watcher/oslo-config-generator/watcher.conf +0 -0
- {python_watcher-15.1.0.data → python_watcher-15.1.1.data}/data/etc/watcher/oslo-policy-generator/watcher-policy-generator.conf +0 -0
- {python_watcher-15.1.0.data → python_watcher-15.1.1.data}/scripts/watcher-api-wsgi +0 -0
- {python_watcher-15.1.0.dist-info → python_watcher-15.1.1.dist-info}/entry_points.txt +0 -0
- {python_watcher-15.1.0.dist-info → python_watcher-15.1.1.dist-info}/licenses/AUTHORS +0 -0
- {python_watcher-15.1.0.dist-info → python_watcher-15.1.1.dist-info}/licenses/LICENSE +0 -0
- {python_watcher-15.1.0.dist-info → python_watcher-15.1.1.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-watcher
|
|
3
|
-
Version: 15.1.
|
|
3
|
+
Version: 15.1.1
|
|
4
4
|
Summary: OpenStack Watcher provides a flexible and scalable resource optimization service for multi-tenant OpenStack-based clouds.
|
|
5
5
|
Home-page: https://docs.openstack.org/watcher/latest/
|
|
6
6
|
Author: OpenStack
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
python_watcher-15.1.
|
|
2
|
-
python_watcher-15.1.
|
|
3
|
-
python_watcher-15.1.
|
|
4
|
-
python_watcher-15.1.
|
|
5
|
-
python_watcher-15.1.
|
|
6
|
-
python_watcher-15.1.
|
|
7
|
-
python_watcher-15.1.
|
|
1
|
+
python_watcher-15.1.1.data/data/etc/apache2/watcher,sha256=urybYUTb0kAVIvbm4WK5oDfXR82QSK2lyMl4Z9dCz4Q,1190
|
|
2
|
+
python_watcher-15.1.1.data/data/etc/watcher/README-watcher.conf.txt,sha256=EIa_sjXDyl1XSg0ovsFwUjqw0euS0N2PYn4HtyBGJME,131
|
|
3
|
+
python_watcher-15.1.1.data/data/etc/watcher/oslo-config-generator/watcher.conf,sha256=O7g1DZqgXvbQgkBfot0dR1ar1d-3SBkOU_ZK4Dz7SFc,406
|
|
4
|
+
python_watcher-15.1.1.data/data/etc/watcher/oslo-policy-generator/watcher-policy-generator.conf,sha256=ZfIuVdtrj7IZm2BrRQvAW3JOL_kqCiILf-vBmwX3ufw,76
|
|
5
|
+
python_watcher-15.1.1.data/scripts/watcher-api-wsgi,sha256=YcIpUO3iRVW6Fu3Md_ooAeHkmJJasG031naMdf8cv2A,1829
|
|
6
|
+
python_watcher-15.1.1.dist-info/licenses/AUTHORS,sha256=_WO5_45pJAVeBpD3J-G8AFeMPJyA5OV0Fzms6SOsqO4,8168
|
|
7
|
+
python_watcher-15.1.1.dist-info/licenses/LICENSE,sha256=XfKg2H1sVi8OoRxoisUlMqoo10TKvHmU_wU39ks7MyA,10143
|
|
8
8
|
watcher/__init__.py,sha256=Vs2olXPZ_IAV1rukHYRl3ITzYFBNXX9mIUtFXkMWYwg,640
|
|
9
9
|
watcher/_i18n.py,sha256=-25OcpwjDCiYdFIUJh86S7Sg0fs3wMIIjEreaGUyCyY,1295
|
|
10
10
|
watcher/eventlet.py,sha256=z6WuDS9HJAq6vRZCg5ozYpWCs3lwhDuv9R70bj8tpQ0,1476
|
|
@@ -77,7 +77,7 @@ watcher/cmd/eventlet/api.py,sha256=n_MTSrrNGQH7EN4q9BFpeQFcfTU0OEAGdD97TdcLmxc,1
|
|
|
77
77
|
watcher/cmd/eventlet/applier.py,sha256=5qprJMg2_ypyAawaoOiZiaeJ8cyCVCnud3F9apyXorg,1335
|
|
78
78
|
watcher/cmd/eventlet/decisionengine.py,sha256=_uZFvf5KRusXcr91ZvQGgQuxgQd_Q_z-vj9xVTnCOLY,1455
|
|
79
79
|
watcher/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
80
|
-
watcher/common/cinder_helper.py,sha256=
|
|
80
|
+
watcher/common/cinder_helper.py,sha256=fjFRddZKT2zefMLa0SttF6sNvTJAyO1V8Xir3IVNaJ0,13336
|
|
81
81
|
watcher/common/clients.py,sha256=Gv6RUJ-HB-vNqriApEn6syn_3QntxmpNHuCtTeQ8998,13920
|
|
82
82
|
watcher/common/config.py,sha256=2_9hklnzAICpZAIDQmxoE8d8qMFS03sjkoVdXPLchmU,1465
|
|
83
83
|
watcher/common/context.py,sha256=7ZZv4FMSKZZQ00NiqZFAsmico21O05UaGlc46U50_6U,3188
|
|
@@ -92,7 +92,7 @@ watcher/common/placement_helper.py,sha256=YP02OHiZOCaJODPAE2QRojf4PKJDlctW-dFn9a
|
|
|
92
92
|
watcher/common/policy.py,sha256=8WGBY79RJItBN4RphBEacYj7JKtSHJLoVw3WW7oM5oA,4893
|
|
93
93
|
watcher/common/rpc.py,sha256=eM0yHwmMDKHq29oualMp3lbVhEol2ffGTZdIJ9CgO_4,4357
|
|
94
94
|
watcher/common/scheduling.py,sha256=kIWt7-vNg7wrebKEP4Xkig5r_DKfY3EnB4E4KCo3-Bo,1986
|
|
95
|
-
watcher/common/service.py,sha256=
|
|
95
|
+
watcher/common/service.py,sha256=nFyaWGiCrlJawrR73EKNe10jfLQbNjp47qSRiwz6gD0,9730
|
|
96
96
|
watcher/common/service_manager.py,sha256=PQwa-58CTLD0bWS_5i9KigSTp9OGeP7m7D1oMg3f50A,1421
|
|
97
97
|
watcher/common/utils.py,sha256=PCPgzMtVfZR9613P7guKv1DJljK_4e0TbW7LgMXmwcg,6038
|
|
98
98
|
watcher/common/loader/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -129,11 +129,11 @@ watcher/conf/decision_engine.py,sha256=dh-3zFzATi8H-CYnwkxCEcHb5XV3qrSpMhWXheGuC
|
|
|
129
129
|
watcher/conf/exception.py,sha256=_VAlIawyQZGHbn8zFzXsNaOiCv5miZD9VGR3HJn2Y1Q,989
|
|
130
130
|
watcher/conf/glance_client.py,sha256=7ftFKm_Y4kf9ZYy7HVZkr0_h3EfmTbNoa2z_iIcMeHs,1589
|
|
131
131
|
watcher/conf/gnocchi_client.py,sha256=wb643c4R_G9oY1le3SPHQ679ErbSqtPRivC4VJWrnmA,1589
|
|
132
|
-
watcher/conf/grafana_client.py,sha256=
|
|
132
|
+
watcher/conf/grafana_client.py,sha256=FOG9dDck_PYptoX6cGsyTXdzewIoHO3kbbxLHrgjIXA,6936
|
|
133
133
|
watcher/conf/grafana_translators.py,sha256=5mqq-ZUYpiOaOtm7GRbxiyiJQFw_GFb8JynGhCMAwyE,1651
|
|
134
134
|
watcher/conf/ironic_client.py,sha256=6VXcRcPtkBtXrXS8xIMRYPzqCeQdXIKtFdTYq-YzC3E,1587
|
|
135
135
|
watcher/conf/keystone_client.py,sha256=zELzoqwBuWLm2S27XyzDZdlmBZBkOyAfxileyjuNJaw,1350
|
|
136
|
-
watcher/conf/maas_client.py,sha256=
|
|
136
|
+
watcher/conf/maas_client.py,sha256=99UaXMNg_1GPyfU2q4TjjHpRmI6zAStcrYzvo6lDyyg,1305
|
|
137
137
|
watcher/conf/models.py,sha256=Fo1zQMcUjtEIroMZ4mx3kRLr2A_-3JldMZJQZ7SKIko,1390
|
|
138
138
|
watcher/conf/monasca_client.py,sha256=8agQc5E5PJ2Na7MRTj9OCJbBd8coF1Ug1GvzG-GJMxw,1602
|
|
139
139
|
watcher/conf/neutron_client.py,sha256=MrrdfkephVKwZjxdN7Jh1oIzR8bymsb8CB8ixs55e1Q,1604
|
|
@@ -209,7 +209,7 @@ watcher/decision_engine/messaging/audit_endpoint.py,sha256=D_GdRWpYHS8mKSvpbENd7
|
|
|
209
209
|
watcher/decision_engine/messaging/data_model_endpoint.py,sha256=bh_ViPvF7I_Nha32eSqsRpDt8FPomM78PENZkGOyDYs,2180
|
|
210
210
|
watcher/decision_engine/model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
211
211
|
watcher/decision_engine/model/base.py,sha256=epNLXZ-bxuglMdyNe9oQawAfBlzip7foRzqu9FOPU-k,1131
|
|
212
|
-
watcher/decision_engine/model/model_root.py,sha256=
|
|
212
|
+
watcher/decision_engine/model/model_root.py,sha256=cE8B8mdwqTOHaQjqJIhtGQm--utSvC8-J75RwOt8PGQ,25079
|
|
213
213
|
watcher/decision_engine/model/collector/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
214
214
|
watcher/decision_engine/model/collector/base.py,sha256=Nv2vbhUXU7jRw-btFKAyP_6ev0OB1R2WCQdpufRA124,9593
|
|
215
215
|
watcher/decision_engine/model/collector/cinder.py,sha256=gZ_NpfoJ9BNVTELd73Tw9cLXtpti8ToxmbY3iWZNLHY,11684
|
|
@@ -218,7 +218,7 @@ watcher/decision_engine/model/collector/manager.py,sha256=3dWsClSXB1hXu8mUbTDuoa
|
|
|
218
218
|
watcher/decision_engine/model/collector/nova.py,sha256=7Tj8OwQVgkIR2cBsbHPNxM6SFmVdD0juqerDg0eS0aY,21882
|
|
219
219
|
watcher/decision_engine/model/element/__init__.py,sha256=R3XmeW4-ufWnbWruWGLR4SQJP9olmBLADRfyrvP-PaE,1320
|
|
220
220
|
watcher/decision_engine/model/element/baremetal_resource.py,sha256=WC6CkGhruAXIiynWDsvKbV3VdvDihBMy3YNTa18Q5bg,924
|
|
221
|
-
watcher/decision_engine/model/element/base.py,sha256=
|
|
221
|
+
watcher/decision_engine/model/element/base.py,sha256=3J5bFef8-y3xwrrS87mVKGt5p7X3mBnUYJvDH6q4eZ8,2247
|
|
222
222
|
watcher/decision_engine/model/element/compute_resource.py,sha256=yeFx-ZQxryLja0oKyJubCc2XQG4i9HL4fpspWhJaSsM,860
|
|
223
223
|
watcher/decision_engine/model/element/instance.py,sha256=pZMxlfALbX34eN0tO4JmxBXEjrKvkq6NMTtj9-pgL7k,2325
|
|
224
224
|
watcher/decision_engine/model/element/node.py,sha256=p8uUG97FJIMBqjUUcLWxEnDFUqJEM6briS8aW_Cp4_A,3777
|
|
@@ -277,7 +277,7 @@ watcher/decision_engine/strategy/strategies/uniform_airflow.py,sha256=MzCCmXirah
|
|
|
277
277
|
watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py,sha256=-usWMjqFcbTSWbGKNmsSlZ-0wnMNwLv2w22Fu6nIqyw,27190
|
|
278
278
|
watcher/decision_engine/strategy/strategies/workload_balance.py,sha256=QFOr-UwJkv0XrYPpm-oZmbEoznDyk2yTDnGHJvkzCe4,15893
|
|
279
279
|
watcher/decision_engine/strategy/strategies/workload_stabilization.py,sha256=Lfn51YMJTxS6n3AwGe-Dc0oVsMCMLXypoTwfevlOl88,25445
|
|
280
|
-
watcher/decision_engine/strategy/strategies/zone_migration.py,sha256=
|
|
280
|
+
watcher/decision_engine/strategy/strategies/zone_migration.py,sha256=rWrrOauKtXuH6YK-xg9BIISZN2u5b6dcKgzd75suZ98,35867
|
|
281
281
|
watcher/hacking/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
282
282
|
watcher/hacking/checks.py,sha256=D-PG1fgVyU5TIKVmZcJ0CXbmLx-8_ivU5vCKQvtZkjk,10383
|
|
283
283
|
watcher/locale/de/LC_MESSAGES/watcher.po,sha256=-EqexOllNxp5DIvacuQAKPVDB8NASduyQXOpoXRMbB4,26317
|
|
@@ -325,7 +325,7 @@ watcher/tests/api/v1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hS
|
|
|
325
325
|
watcher/tests/api/v1/test_actions.py,sha256=l27mglSLBQE_A-eQFv5rUzu4nwS3CaIQtLo37JMxgqI,33989
|
|
326
326
|
watcher/tests/api/v1/test_actions_plans.py,sha256=L7xC9ZANo41qfnmBL3RNQf-jAb6e8NaIQekTPpp0sSk,33795
|
|
327
327
|
watcher/tests/api/v1/test_audit_templates.py,sha256=Snj3vFLthLDciTlLUFu80Rj2u91mnVvHa5afY6LRRIo,38366
|
|
328
|
-
watcher/tests/api/v1/test_audits.py,sha256=
|
|
328
|
+
watcher/tests/api/v1/test_audits.py,sha256=T4_8hu4-L-bQIXCseVF0bEJUHtTxoV06IN7gB69PZcA,65828
|
|
329
329
|
watcher/tests/api/v1/test_data_model.py,sha256=jR5yf5jSxbwb89N5XxlZcqDdxrLCuKuCxudZ6xCZHdM,6635
|
|
330
330
|
watcher/tests/api/v1/test_goals.py,sha256=vveUL0rQHv0LV9xa1T4EQCNpyAbdQMFF14_HHKsyHUs,7335
|
|
331
331
|
watcher/tests/api/v1/test_microversions.py,sha256=0Oo_AZqg-Mvmc5GGVvIPkWvr2Czl1ku4tUPlEh8WclE,4998
|
|
@@ -366,7 +366,7 @@ watcher/tests/cmd/test_db_manage.py,sha256=itbu-PM6I5OAP_nvWUxGYDfl5tE2e58eVafwx
|
|
|
366
366
|
watcher/tests/cmd/test_decision_engine.py,sha256=7hSHAPLQo2WZxQoY-pEfuVMjpnAJytyK7SUizin54Ig,2267
|
|
367
367
|
watcher/tests/cmd/test_status.py,sha256=EBwifVusDZuTOpiVGKNqrmJv6hK8ZlZcngm15Jntyc8,1586
|
|
368
368
|
watcher/tests/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
369
|
-
watcher/tests/common/test_cinder_helper.py,sha256=
|
|
369
|
+
watcher/tests/common/test_cinder_helper.py,sha256=iFf6qgvyF-VfOg3jnm2Xj3MoQVIBP6h3-vJZiYWIYXg,32071
|
|
370
370
|
watcher/tests/common/test_clients.py,sha256=SD6qQsND91faF9Q1eTR8TIEFiAoiqKZef6FgBKIFqRw,17984
|
|
371
371
|
watcher/tests/common/test_executor.py,sha256=W1F7ANlyTEX-v8wycQXE5SQ0yIkagvh3w2u7xk7x4p0,1404
|
|
372
372
|
watcher/tests/common/test_ironic_helper.py,sha256=Ut-bpvPBQJWdu9xWPvzQ0sFEimuHU0_i3bC3T9aHvE0,2162
|
|
@@ -375,7 +375,7 @@ watcher/tests/common/test_nova_helper.py,sha256=Fjtp-RFr-M46U_6hnkS5Z2GhDNEBvdmH
|
|
|
375
375
|
watcher/tests/common/test_oslo_service_helper.py,sha256=aG28fDYYtCDGrSPrRbcPBKeFtq4v8KBYMslXzLYLXlQ,1677
|
|
376
376
|
watcher/tests/common/test_placement_helper.py,sha256=Y4P2YbWhC0itWGJbaDkoua9ot0XknXuM8disvceIXQQ,11761
|
|
377
377
|
watcher/tests/common/test_scheduling.py,sha256=pOokZK33L9eaXUX42OZmBukkIwu91wAthSG4XwezXU4,2235
|
|
378
|
-
watcher/tests/common/test_service.py,sha256=
|
|
378
|
+
watcher/tests/common/test_service.py,sha256=zQ4kKVr6BsP-4cvlGS3GL2GyWzt2Zu0UhLmz2W97R_g,4401
|
|
379
379
|
watcher/tests/common/test_utils.py,sha256=Mq-51gFQv2EPdGaO44_py8ewdbObvnhwE4fpnzPTQPM,1697
|
|
380
380
|
watcher/tests/common/loader/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
381
381
|
watcher/tests/common/loader/test_loader.py,sha256=mBnhe2apSmqpEL-8QZUlRDhDuWEEGo7iZQxpZIuGCWM,3612
|
|
@@ -443,7 +443,7 @@ watcher/tests/decision_engine/model/faker_cluster_state.py,sha256=p3SW99eEzDQWId
|
|
|
443
443
|
watcher/tests/decision_engine/model/gnocchi_metrics.py,sha256=Fs5gGrAgxHA2eENzJbWCMDEUNMZPxy2qUwhLFNWTS-M,9357
|
|
444
444
|
watcher/tests/decision_engine/model/monasca_metrics.py,sha256=s-voe8Cu7Su2_Tn_sp3dSxWTPI3zgFd9Wtrf9CZ7Wu4,4121
|
|
445
445
|
watcher/tests/decision_engine/model/test_element.py,sha256=9PggbUFDshEEcbVYhU-49TKQgtufHIlTfqoJNI8Czb4,6000
|
|
446
|
-
watcher/tests/decision_engine/model/test_model.py,sha256=
|
|
446
|
+
watcher/tests/decision_engine/model/test_model.py,sha256=BLWwwRXXrc4TcoUPRwpHe4YXDGgyW1rt8XR7oJEWLeo,21982
|
|
447
447
|
watcher/tests/decision_engine/model/data/ironic_scenario_1.xml,sha256=BxJ0R1FlqJa4ryyMPY81rswjW9WOSQxpsbX5lhL_Ono,451
|
|
448
448
|
watcher/tests/decision_engine/model/data/scenario_1.xml,sha256=7vAaXW--mVZPuhXMh5bIhj072IY1SQVnphnh6StZHJs,10426
|
|
449
449
|
watcher/tests/decision_engine/model/data/scenario_10.xml,sha256=XkdELZDxrUIrAnEqCxHbav0g_8FKzP_zbGuHrWv_4r8,4984
|
|
@@ -550,7 +550,7 @@ watcher/tests/decision_engine/strategy/strategies/test_uniform_airflow.py,sha256
|
|
|
550
550
|
watcher/tests/decision_engine/strategy/strategies/test_vm_workload_consolidation.py,sha256=AXY55dBg_8w37qpy0BT89ScUo81BUCZXwZO957jTgEE,19203
|
|
551
551
|
watcher/tests/decision_engine/strategy/strategies/test_workload_balance.py,sha256=m2VljGe-9IJBBRyJwkseDiRxLB1z510tQL3orGyG470,8380
|
|
552
552
|
watcher/tests/decision_engine/strategy/strategies/test_workload_stabilization.py,sha256=eGr_PX8dpM4oTlC-GFrimDN1kQbKpNFzA_C5c4mo250,14313
|
|
553
|
-
watcher/tests/decision_engine/strategy/strategies/test_zone_migration.py,sha256=
|
|
553
|
+
watcher/tests/decision_engine/strategy/strategies/test_zone_migration.py,sha256=kH0fqMw0URbBJG9BSnZg2qvTMxXZEuJhQWyTNFOc70Y,72059
|
|
554
554
|
watcher/tests/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
555
555
|
watcher/tests/fixtures/watcher.py,sha256=9BqKxgaBLWG0LEss-rnRQu3CuB4Z6hzry6hjI0UNxhk,4923
|
|
556
556
|
watcher/tests/notifications/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -574,9 +574,9 @@ watcher/tests/objects/test_strategy.py,sha256=WoJYFQD0LU7vkUT2MMy1bx3S6eY458aTIC
|
|
|
574
574
|
watcher/tests/objects/utils.py,sha256=WtBFlXWIBRvl1keAjz1GtrkUWppil2pFIA-RngcqA5c,8058
|
|
575
575
|
watcher/wsgi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
576
576
|
watcher/wsgi/api.py,sha256=F1s1Q_CMuRdgS_f7Gl3XzavjZqdxqO9IHLxSWl6NOO4,789
|
|
577
|
-
python_watcher-15.1.
|
|
578
|
-
python_watcher-15.1.
|
|
579
|
-
python_watcher-15.1.
|
|
580
|
-
python_watcher-15.1.
|
|
581
|
-
python_watcher-15.1.
|
|
582
|
-
python_watcher-15.1.
|
|
577
|
+
python_watcher-15.1.1.dist-info/METADATA,sha256=6uqqmtya0CNSmsp7JIq1pubEj-Hp97Yqy3Aluj_JOHA,4110
|
|
578
|
+
python_watcher-15.1.1.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
579
|
+
python_watcher-15.1.1.dist-info/entry_points.txt,sha256=neFmDxaEe7U6uSPJEFcai3Aw4cyI_aFnHXDoWBK1fqI,4464
|
|
580
|
+
python_watcher-15.1.1.dist-info/pbr.json,sha256=Cbz81KbjaXA-Il88VMNiuOYe2l9fdNhmiCGFdY4tpZU,47
|
|
581
|
+
python_watcher-15.1.1.dist-info/top_level.txt,sha256=O2B7Sbzshq5s-tZxb2a7VETvMDiJItSYieAiJ6Jm5yY,8
|
|
582
|
+
python_watcher-15.1.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"git_version": "8e27e90a", "is_release": true}
|
watcher/common/cinder_helper.py
CHANGED
|
@@ -94,18 +94,6 @@ class CinderHelper(object):
|
|
|
94
94
|
except cinder_exception.NotFound:
|
|
95
95
|
return self.cinder.volumes.find(name=volume)
|
|
96
96
|
|
|
97
|
-
def backendname_from_poolname(self, poolname):
|
|
98
|
-
"""Get backendname from poolname"""
|
|
99
|
-
# pooolname formatted as host@backend#pool since ocata
|
|
100
|
-
# as of ocata, may as only host
|
|
101
|
-
backend = poolname.split('#')[0]
|
|
102
|
-
backendname = ""
|
|
103
|
-
try:
|
|
104
|
-
backendname = backend.split('@')[1]
|
|
105
|
-
except IndexError:
|
|
106
|
-
pass
|
|
107
|
-
return backendname
|
|
108
|
-
|
|
109
97
|
def _has_snapshot(self, volume):
|
|
110
98
|
"""Judge volume has a snapshot"""
|
|
111
99
|
volume = self.get_volume(volume)
|
|
@@ -132,6 +120,51 @@ class CinderHelper(object):
|
|
|
132
120
|
else:
|
|
133
121
|
return True
|
|
134
122
|
|
|
123
|
+
def _check_backend_matches_type(self, pool, volume_type):
|
|
124
|
+
"""Check if a storage pool matches volume type requirements.
|
|
125
|
+
|
|
126
|
+
Verifies that all extra_specs properties defined in the volume
|
|
127
|
+
type are present in the pool's capabilities with matching values.
|
|
128
|
+
|
|
129
|
+
:param pool: Storage pool dictionary with capabilities
|
|
130
|
+
:param volume_type: Volume type object with extra_specs
|
|
131
|
+
:returns: True if pool matches all volume type requirements,
|
|
132
|
+
False otherwise
|
|
133
|
+
"""
|
|
134
|
+
for field_name, field_value in volume_type.extra_specs.items():
|
|
135
|
+
pool_value = pool.get("capabilities", {}).get(field_name)
|
|
136
|
+
if pool_value is not None and field_value != pool_value:
|
|
137
|
+
# the property associated with the volume type is
|
|
138
|
+
# not defined in the pool, so the type can't be used in the
|
|
139
|
+
# pool
|
|
140
|
+
LOG.debug(
|
|
141
|
+
f"property {field_name} with value {field_value} "
|
|
142
|
+
f"does not match value {pool_value} from pool "
|
|
143
|
+
f"{pool['name']}"
|
|
144
|
+
)
|
|
145
|
+
return False
|
|
146
|
+
return True
|
|
147
|
+
|
|
148
|
+
def get_volume_types_for_pool(self, pool):
|
|
149
|
+
"""Return a list of volume types that can be associated with a pool.
|
|
150
|
+
|
|
151
|
+
:param pool: Storage pool dictionary with capabilities
|
|
152
|
+
:returns: List of volume types than can be scheduled in the input pool
|
|
153
|
+
"""
|
|
154
|
+
volume_type_list = self.get_volume_type_list()
|
|
155
|
+
|
|
156
|
+
pool_volume_types = []
|
|
157
|
+
for volume_type in volume_type_list:
|
|
158
|
+
if not volume_type.extra_specs:
|
|
159
|
+
# if there are no properties associated with the volume type
|
|
160
|
+
# it can be used in any pool
|
|
161
|
+
pool_volume_types.append(volume_type.name)
|
|
162
|
+
continue
|
|
163
|
+
if self._check_backend_matches_type(pool, volume_type):
|
|
164
|
+
pool_volume_types.append(volume_type.name)
|
|
165
|
+
|
|
166
|
+
return pool_volume_types
|
|
167
|
+
|
|
135
168
|
def check_volume_deleted(self, volume, retry=120, retry_interval=10):
|
|
136
169
|
"""Check volume has been deleted"""
|
|
137
170
|
volume = self.get_volume(volume)
|
|
@@ -224,11 +257,20 @@ class CinderHelper(object):
|
|
|
224
257
|
def migrate(self, volume, dest_node):
|
|
225
258
|
"""Migrate volume to dest_node"""
|
|
226
259
|
volume = self.get_volume(volume)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if volume.volume_type not in
|
|
260
|
+
pool_dict = self.get_storage_pool_by_name(dest_node).to_dict()
|
|
261
|
+
dest_types = self.get_volume_types_for_pool(pool_dict)
|
|
262
|
+
if volume.volume_type not in dest_types:
|
|
230
263
|
raise exception.Invalid(
|
|
231
|
-
message=(
|
|
264
|
+
message=(
|
|
265
|
+
_(
|
|
266
|
+
"Volume type '%(volume_type)s' is not compatible with "
|
|
267
|
+
"destination pool '%(pool_name)s'"
|
|
268
|
+
) % {
|
|
269
|
+
'volume_type': volume.volume_type,
|
|
270
|
+
'pool_name': dest_node
|
|
271
|
+
}
|
|
272
|
+
)
|
|
273
|
+
)
|
|
232
274
|
|
|
233
275
|
source_node = getattr(volume, 'os-vol-host-attr:host')
|
|
234
276
|
LOG.debug("Volume %(volume)s found on host '%(host)s'.",
|
watcher/common/service.py
CHANGED
|
@@ -241,7 +241,7 @@ class Service(service.ServiceBase):
|
|
|
241
241
|
)
|
|
242
242
|
|
|
243
243
|
def start(self):
|
|
244
|
-
LOG.debug("Connecting to
|
|
244
|
+
LOG.debug("Connecting to messaging transport")
|
|
245
245
|
if self.conductor_topic_handler:
|
|
246
246
|
self.conductor_topic_handler.start()
|
|
247
247
|
if self.notification_handler:
|
|
@@ -250,7 +250,7 @@ class Service(service.ServiceBase):
|
|
|
250
250
|
self.heartbeat.start()
|
|
251
251
|
|
|
252
252
|
def stop(self):
|
|
253
|
-
LOG.debug("Disconnecting from
|
|
253
|
+
LOG.debug("Disconnecting from messaging transport")
|
|
254
254
|
if self.conductor_topic_handler:
|
|
255
255
|
self.conductor_topic_handler.stop()
|
|
256
256
|
if self.notification_handler:
|
watcher/conf/grafana_client.py
CHANGED
|
@@ -29,6 +29,7 @@ GRAFANA_CLIENT_OPTS = [
|
|
|
29
29
|
# A similar structure to the database_map would solve this.
|
|
30
30
|
cfg.StrOpt('token',
|
|
31
31
|
default=None,
|
|
32
|
+
secret=True,
|
|
32
33
|
help="Authentication token to gain access"),
|
|
33
34
|
# TODO(Dantali0n) each individual metric could have its own base url.
|
|
34
35
|
# A similar structure to the database_map would solve this.
|
watcher/conf/maas_client.py
CHANGED
|
@@ -51,16 +51,19 @@ class Element(base.WatcherObject, base.WatcherObjectDictCompat,
|
|
|
51
51
|
|
|
52
52
|
def as_xml_element(self):
|
|
53
53
|
sorted_fieldmap = []
|
|
54
|
+
element_name = self.__class__.__name__
|
|
54
55
|
for field in self.fields:
|
|
55
56
|
try:
|
|
56
57
|
value = str(self[field])
|
|
57
58
|
sorted_fieldmap.append((field, value))
|
|
59
|
+
except NotImplementedError:
|
|
60
|
+
LOG.debug("Attribute %s for object %s: %s is not provided",
|
|
61
|
+
field, element_name, self)
|
|
58
62
|
except Exception as exc:
|
|
59
63
|
LOG.exception(exc)
|
|
60
64
|
|
|
61
65
|
attrib = collections.OrderedDict(sorted_fieldmap)
|
|
62
66
|
|
|
63
|
-
element_name = self.__class__.__name__
|
|
64
67
|
instance_el = etree.Element(element_name, attrib=attrib)
|
|
65
68
|
|
|
66
69
|
return instance_el
|
|
@@ -269,7 +269,14 @@ class ModelRoot(nx.DiGraph, base.Model):
|
|
|
269
269
|
in_dict = {}
|
|
270
270
|
for field in cn.fields:
|
|
271
271
|
new_name = "node_"+str(field)
|
|
272
|
-
|
|
272
|
+
try:
|
|
273
|
+
in_dict[new_name] = cn[field]
|
|
274
|
+
# NotImplementedError means field not assigned
|
|
275
|
+
# which is really not an issue as it can be an
|
|
276
|
+
# optional field
|
|
277
|
+
except NotImplementedError:
|
|
278
|
+
LOG.debug("Attribute %s for Compute: %s is not provided",
|
|
279
|
+
field, cn.hostname)
|
|
273
280
|
node_instances = self.get_node_instances(cn)
|
|
274
281
|
if not node_instances:
|
|
275
282
|
deep_in_dict = in_dict.copy()
|
|
@@ -278,7 +285,11 @@ class ModelRoot(nx.DiGraph, base.Model):
|
|
|
278
285
|
for instance in sorted(node_instances, key=lambda x: x.uuid):
|
|
279
286
|
for field in instance.fields:
|
|
280
287
|
new_name = "server_"+str(field)
|
|
281
|
-
|
|
288
|
+
try:
|
|
289
|
+
in_dict[new_name] = instance[field]
|
|
290
|
+
except NotImplementedError:
|
|
291
|
+
LOG.debug("Attribute %s for Instance: %s is not "
|
|
292
|
+
"provided", field, instance.uuid)
|
|
282
293
|
if in_dict != {}:
|
|
283
294
|
deep_in_dict = in_dict.copy()
|
|
284
295
|
ret_list.append(deep_in_dict)
|
|
@@ -381,16 +381,36 @@ class ZoneMigration(base.ZoneMigrationBaseStrategy):
|
|
|
381
381
|
if node.get("src_node") == src_node:
|
|
382
382
|
return node.get("dst_node")
|
|
383
383
|
|
|
384
|
-
def get_dst_pool_and_type(self, src_pool):
|
|
384
|
+
def get_dst_pool_and_type(self, src_pool, src_type):
|
|
385
385
|
"""Get destination pool and type from self.migration_storage_pools
|
|
386
386
|
|
|
387
387
|
:param src_pool: storage pool name
|
|
388
|
-
:
|
|
388
|
+
:param src_type: storage volume type
|
|
389
|
+
:returns: set of storage dst pool name and dst volume type name, any
|
|
390
|
+
of those values can be None if no match for it is found in the
|
|
391
|
+
input_parameters
|
|
389
392
|
"""
|
|
393
|
+
src_pool_type_inputs = []
|
|
394
|
+
src_pool_inputs = []
|
|
390
395
|
for pool in self.migrate_storage_pools:
|
|
396
|
+
if pool.get("src_type") is None:
|
|
397
|
+
src_pool_inputs.append(pool)
|
|
398
|
+
else:
|
|
399
|
+
src_pool_type_inputs.append(pool)
|
|
400
|
+
|
|
401
|
+
pool_to_check = (src_pool, src_type)
|
|
402
|
+
# First pass: prefer exact match (both src_pool and src_type)
|
|
403
|
+
for pool in src_pool_type_inputs:
|
|
404
|
+
user_pool = (pool.get("src_pool"), pool.get("src_type"))
|
|
405
|
+
if pool_to_check == user_pool:
|
|
406
|
+
return (pool.get("dst_pool"), pool.get("dst_type"))
|
|
407
|
+
|
|
408
|
+
# Second pass: fall back to matching just src_pool
|
|
409
|
+
for pool in src_pool_inputs:
|
|
391
410
|
if pool.get("src_pool") == src_pool:
|
|
392
|
-
return (pool.get("dst_pool",
|
|
393
|
-
|
|
411
|
+
return (pool.get("dst_pool"), pool.get("dst_type"))
|
|
412
|
+
|
|
413
|
+
return (None, None)
|
|
394
414
|
|
|
395
415
|
def volumes_migration(self, volumes, action_counter, instance_targets=[]):
|
|
396
416
|
instance_target_ids = {instance.id for instance in instance_targets}
|
|
@@ -406,17 +426,20 @@ class ZoneMigration(base.ZoneMigrationBaseStrategy):
|
|
|
406
426
|
continue
|
|
407
427
|
|
|
408
428
|
src_type = volume.volume_type
|
|
409
|
-
dst_pool, dst_type = self.get_dst_pool_and_type(pool)
|
|
429
|
+
dst_pool, dst_type = self.get_dst_pool_and_type(pool, src_type)
|
|
410
430
|
LOG.debug(src_type)
|
|
411
431
|
LOG.debug("%s %s", dst_pool, dst_type)
|
|
412
432
|
|
|
413
|
-
if src_type == dst_type:
|
|
433
|
+
if dst_type is None or src_type == dst_type:
|
|
414
434
|
if dst_pool is None:
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
435
|
+
type_msg = (
|
|
436
|
+
"" if dst_type is None else
|
|
437
|
+
f"it already has type {dst_type} and ")
|
|
438
|
+
LOG.info(
|
|
439
|
+
"volume %s will not be migrated because %s'dst_pool' "
|
|
440
|
+
"was not specified for src_pool %s",
|
|
441
|
+
volume.name, type_msg, pool
|
|
442
|
+
)
|
|
420
443
|
continue
|
|
421
444
|
self._volume_migrate(volume, dst_pool)
|
|
422
445
|
else:
|
|
@@ -1394,6 +1394,39 @@ class TestAuditZoneMigration(TestPostBase):
|
|
|
1394
1394
|
self.assertIn(expected_error_msg, response.json['error_message'])
|
|
1395
1395
|
mock_trigger_audit.assert_not_called()
|
|
1396
1396
|
|
|
1397
|
+
@mock.patch.object(deapi.DecisionEngineAPI, 'trigger_audit')
|
|
1398
|
+
def test_create_audit_zone_migration_no_dst_pool_dst_type(
|
|
1399
|
+
self, mock_trigger_audit
|
|
1400
|
+
):
|
|
1401
|
+
"""Verify zone migration rejects no destination parameters.
|
|
1402
|
+
|
|
1403
|
+
Tests that an audit creation fails with BAD_REQUEST when neither
|
|
1404
|
+
dst_type nor dst_pool are passed in the input parameters. This
|
|
1405
|
+
validates the schema correctly enforces that the at least one
|
|
1406
|
+
destination parameters is present.
|
|
1407
|
+
"""
|
|
1408
|
+
mock_trigger_audit.return_value = mock.ANY
|
|
1409
|
+
zm_params = {
|
|
1410
|
+
'storage_pools': [
|
|
1411
|
+
{"src_type": "src_type_name",
|
|
1412
|
+
"src_pool": "src_pool_name"}
|
|
1413
|
+
]
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
audit_input_dict = self._prepare_audit_params(zm_params)
|
|
1417
|
+
|
|
1418
|
+
response = self.post_json('/audits', audit_input_dict,
|
|
1419
|
+
expect_errors=True)
|
|
1420
|
+
self.assertEqual("application/json", response.content_type)
|
|
1421
|
+
self.assertEqual(HTTPStatus.BAD_REQUEST, response.status_int)
|
|
1422
|
+
expected_error_msgs = (
|
|
1423
|
+
"is not valid under any of the given schemas",
|
|
1424
|
+
"Failed validating 'oneOf' in schema"
|
|
1425
|
+
)
|
|
1426
|
+
for expected_error_msg in expected_error_msgs:
|
|
1427
|
+
self.assertIn(expected_error_msg, response.json['error_message'])
|
|
1428
|
+
mock_trigger_audit.assert_not_called()
|
|
1429
|
+
|
|
1397
1430
|
@mock.patch.object(deapi.DecisionEngineAPI, 'trigger_audit')
|
|
1398
1431
|
def test_create_audit_zone_migration_with_multiple_valid_inputs(
|
|
1399
1432
|
self, mock_trigger_audit
|
|
@@ -21,6 +21,9 @@ from http import HTTPStatus
|
|
|
21
21
|
import time
|
|
22
22
|
|
|
23
23
|
from cinderclient import exceptions as cinder_exception
|
|
24
|
+
from cinderclient.v3.pools import Pool as CinderPool
|
|
25
|
+
from cinderclient.v3.volume_types import VolumeType as CinderVolType
|
|
26
|
+
from cinderclient.v3.volumes import Volume as CinderVolume
|
|
24
27
|
|
|
25
28
|
from watcher.common import cinder_helper
|
|
26
29
|
from watcher.common import clients
|
|
@@ -67,8 +70,20 @@ class TestCinderHelper(base.TestCase):
|
|
|
67
70
|
|
|
68
71
|
@staticmethod
|
|
69
72
|
def fake_pool(**kwargs):
|
|
70
|
-
pool = mock.MagicMock()
|
|
73
|
+
pool = mock.MagicMock(spec=CinderPool)
|
|
71
74
|
pool.name = kwargs.get('name', 'host@backend#pool')
|
|
75
|
+
host_backend = pool.name.split('#')[0]
|
|
76
|
+
default_capabilities = {
|
|
77
|
+
'volume_backend_name': host_backend.split('@')[1]
|
|
78
|
+
}
|
|
79
|
+
pool.capabilities = kwargs.get('capabilities', default_capabilities)
|
|
80
|
+
|
|
81
|
+
def _to_dict():
|
|
82
|
+
return {
|
|
83
|
+
'name': pool.name,
|
|
84
|
+
'capabilities': pool.capabilities
|
|
85
|
+
}
|
|
86
|
+
pool.to_dict = _to_dict
|
|
72
87
|
|
|
73
88
|
return pool
|
|
74
89
|
|
|
@@ -98,7 +113,7 @@ class TestCinderHelper(base.TestCase):
|
|
|
98
113
|
|
|
99
114
|
@staticmethod
|
|
100
115
|
def fake_volume_type(**kwargs):
|
|
101
|
-
volume_type = mock.MagicMock()
|
|
116
|
+
volume_type = mock.MagicMock(spec=CinderVolType)
|
|
102
117
|
volume_type.name = kwargs.get('name', 'fake_type')
|
|
103
118
|
extra_specs = {'volume_backend_name': 'backend'}
|
|
104
119
|
volume_type.extra_specs = kwargs.get('extra_specs', extra_specs)
|
|
@@ -133,7 +148,7 @@ class TestCinderHelper(base.TestCase):
|
|
|
133
148
|
|
|
134
149
|
@staticmethod
|
|
135
150
|
def fake_volume(**kwargs):
|
|
136
|
-
volume = mock.MagicMock()
|
|
151
|
+
volume = mock.MagicMock(spec=CinderVolume)
|
|
137
152
|
volume.id = kwargs.get('id', '45a37aeb-95ab-4ddb-a305-7d9f62c2f5ba')
|
|
138
153
|
volume.name = kwargs.get('name', 'fakename')
|
|
139
154
|
volume.size = kwargs.get('size', '1')
|
|
@@ -142,51 +157,72 @@ class TestCinderHelper(base.TestCase):
|
|
|
142
157
|
volume.availability_zone = kwargs.get('availability_zone', 'nova')
|
|
143
158
|
volume.volume_type = kwargs.get('volume_type', 'fake_type')
|
|
144
159
|
volume.migration_status = kwargs.get('migration_status')
|
|
145
|
-
|
|
160
|
+
setattr(
|
|
161
|
+
volume, 'os-vol-host-attr:host',
|
|
162
|
+
kwargs.get('os_vol_host_attr_host')
|
|
163
|
+
)
|
|
146
164
|
return volume
|
|
147
165
|
|
|
148
166
|
@mock.patch.object(time, 'sleep', mock.Mock())
|
|
149
|
-
|
|
167
|
+
@mock.patch.object(cinder_helper.CinderHelper, 'get_storage_pool_by_name')
|
|
168
|
+
def test_migrate_success(self, mock_get_pool, mock_cinder):
|
|
150
169
|
|
|
151
170
|
cinder_util = cinder_helper.CinderHelper()
|
|
152
171
|
|
|
153
|
-
volume = self.fake_volume(
|
|
154
|
-
|
|
155
|
-
|
|
172
|
+
volume = self.fake_volume(
|
|
173
|
+
os_vol_host_attr_host='source_node',
|
|
174
|
+
migration_status='success'
|
|
175
|
+
)
|
|
156
176
|
cinder_util.cinder.volumes.get.return_value = volume
|
|
157
177
|
|
|
158
178
|
volume_type = self.fake_volume_type()
|
|
159
179
|
cinder_util.cinder.volume_types.list.return_value = [volume_type]
|
|
180
|
+
mock_pool = self.fake_pool()
|
|
181
|
+
mock_get_pool.return_value = mock_pool
|
|
160
182
|
|
|
161
183
|
result = cinder_util.migrate(volume, 'host@backend#pool')
|
|
184
|
+
mock_get_pool.assert_called_once_with('host@backend#pool')
|
|
162
185
|
self.assertTrue(result)
|
|
163
186
|
|
|
164
187
|
@mock.patch.object(time, 'sleep', mock.Mock())
|
|
165
|
-
|
|
188
|
+
@mock.patch.object(cinder_helper.CinderHelper, 'get_storage_pool_by_name')
|
|
189
|
+
def test_migrate_fail(self, mock_get_pool, mock_cinder):
|
|
166
190
|
|
|
167
191
|
cinder_util = cinder_helper.CinderHelper()
|
|
168
192
|
|
|
169
193
|
volume = self.fake_volume()
|
|
170
194
|
cinder_util.cinder.volumes.get.return_value = volume
|
|
195
|
+
mock_pool = self.fake_pool()
|
|
196
|
+
mock_get_pool.return_value = mock_pool
|
|
171
197
|
|
|
172
|
-
volume_type = self.fake_volume_type(
|
|
173
|
-
|
|
198
|
+
volume_type = self.fake_volume_type(
|
|
199
|
+
name='notbackend',
|
|
200
|
+
extra_specs={'volume_backend_name': 'diff_backend'}
|
|
201
|
+
)
|
|
174
202
|
cinder_util.cinder.volume_types.list.return_value = [volume_type]
|
|
175
203
|
|
|
176
204
|
self.assertRaisesRegex(
|
|
177
205
|
exception.Invalid,
|
|
178
|
-
"Volume type
|
|
206
|
+
"Volume type 'fake_type' is not compatible with destination "
|
|
207
|
+
"pool 'host@backend#pool'",
|
|
179
208
|
cinder_util.migrate, volume, 'host@backend#pool')
|
|
180
209
|
|
|
181
|
-
volume = self.fake_volume(
|
|
182
|
-
|
|
183
|
-
|
|
210
|
+
volume = self.fake_volume(
|
|
211
|
+
migration_status='error',
|
|
212
|
+
os_vol_host_attr_host='source_node'
|
|
213
|
+
)
|
|
184
214
|
cinder_util.cinder.volumes.get.return_value = volume
|
|
185
215
|
|
|
186
|
-
|
|
216
|
+
# check that a volume type without any volume_backend_name passes the
|
|
217
|
+
# volume type check and proceeds to the migration
|
|
218
|
+
volume_type = self.fake_volume_type(extra_specs={})
|
|
187
219
|
cinder_util.cinder.volume_types.list.return_value = [volume_type]
|
|
188
220
|
|
|
189
221
|
result = cinder_util.migrate(volume, 'host@backend#pool')
|
|
222
|
+
mock_get_pool.assert_called_with('host@backend#pool')
|
|
223
|
+
cinder_util.cinder.volumes.migrate_volume.assert_called_with(
|
|
224
|
+
volume, 'host@backend#pool', False, True
|
|
225
|
+
)
|
|
190
226
|
self.assertFalse(result)
|
|
191
227
|
|
|
192
228
|
@mock.patch.object(time, 'sleep', mock.Mock())
|
|
@@ -607,3 +643,157 @@ class TestCinderHelper(base.TestCase):
|
|
|
607
643
|
"Volume migration error on volume %(volume)s.",
|
|
608
644
|
{'volume': volume.id})
|
|
609
645
|
self.assertFalse(result)
|
|
646
|
+
|
|
647
|
+
def test_get_volume_types_for_pool_matching_backend(
|
|
648
|
+
self, mock_cinder):
|
|
649
|
+
"""Test matching backend and no extra_specs are selected."""
|
|
650
|
+
cinder_util = cinder_helper.CinderHelper()
|
|
651
|
+
pool = self.fake_pool(
|
|
652
|
+
name='host@backend#pool',
|
|
653
|
+
capabilities={'volume_backend_name': 'backend'}
|
|
654
|
+
)
|
|
655
|
+
volume_type1 = self.fake_volume_type(
|
|
656
|
+
name='type_matching',
|
|
657
|
+
extra_specs={'volume_backend_name': 'backend'}
|
|
658
|
+
)
|
|
659
|
+
volume_type2 = self.fake_volume_type(
|
|
660
|
+
name='type_no_specs',
|
|
661
|
+
extra_specs={}
|
|
662
|
+
)
|
|
663
|
+
cinder_util.cinder.volume_types.list.return_value = [
|
|
664
|
+
volume_type1, volume_type2
|
|
665
|
+
]
|
|
666
|
+
result = cinder_util.get_volume_types_for_pool(
|
|
667
|
+
pool.to_dict())
|
|
668
|
+
self.assertEqual(
|
|
669
|
+
sorted(result),
|
|
670
|
+
['type_matching', 'type_no_specs'])
|
|
671
|
+
|
|
672
|
+
def test_get_volume_types_for_pool_different_backend(
|
|
673
|
+
self, mock_cinder):
|
|
674
|
+
"""Test different backend is excluded, no specs is included."""
|
|
675
|
+
cinder_util = cinder_helper.CinderHelper()
|
|
676
|
+
pool = self.fake_pool(
|
|
677
|
+
name='host@backend#pool',
|
|
678
|
+
capabilities={'volume_backend_name': 'backend'}
|
|
679
|
+
)
|
|
680
|
+
volume_type1 = self.fake_volume_type(
|
|
681
|
+
name='type_no_specs',
|
|
682
|
+
extra_specs={}
|
|
683
|
+
)
|
|
684
|
+
volume_type2 = self.fake_volume_type(
|
|
685
|
+
name='type_different_backend',
|
|
686
|
+
extra_specs={'volume_backend_name': 'different_backend'}
|
|
687
|
+
)
|
|
688
|
+
cinder_util.cinder.volume_types.list.return_value = [
|
|
689
|
+
volume_type1, volume_type2
|
|
690
|
+
]
|
|
691
|
+
result = cinder_util.get_volume_types_for_pool(
|
|
692
|
+
pool.to_dict())
|
|
693
|
+
self.assertEqual(result, ['type_no_specs'])
|
|
694
|
+
|
|
695
|
+
def test_get_volume_types_for_pool_matching_capability(
|
|
696
|
+
self, mock_cinder):
|
|
697
|
+
"""Test volume type with matching capability is selected."""
|
|
698
|
+
cinder_util = cinder_helper.CinderHelper()
|
|
699
|
+
pool = self.fake_pool(
|
|
700
|
+
name='host@backend#pool',
|
|
701
|
+
capabilities={'disk_speed': 'fast'}
|
|
702
|
+
)
|
|
703
|
+
volume_type = self.fake_volume_type(
|
|
704
|
+
name='type_fast_disk',
|
|
705
|
+
extra_specs={'disk_speed': 'fast'}
|
|
706
|
+
)
|
|
707
|
+
cinder_util.cinder.volume_types.list.return_value = [
|
|
708
|
+
volume_type]
|
|
709
|
+
result = cinder_util.get_volume_types_for_pool(
|
|
710
|
+
pool.to_dict())
|
|
711
|
+
self.assertEqual(result, ['type_fast_disk'])
|
|
712
|
+
|
|
713
|
+
def test_get_volume_types_for_pool_mismatched_capability(
|
|
714
|
+
self, mock_cinder):
|
|
715
|
+
"""Test volume type with different capability value excluded."""
|
|
716
|
+
cinder_util = cinder_helper.CinderHelper()
|
|
717
|
+
pool = self.fake_pool(
|
|
718
|
+
name='host@backend#pool',
|
|
719
|
+
capabilities={'disk_speed': 'fast'}
|
|
720
|
+
)
|
|
721
|
+
volume_type = self.fake_volume_type(
|
|
722
|
+
name='type_slow_disk',
|
|
723
|
+
extra_specs={'disk_speed': 'slow'}
|
|
724
|
+
)
|
|
725
|
+
cinder_util.cinder.volume_types.list.return_value = [
|
|
726
|
+
volume_type]
|
|
727
|
+
result = cinder_util.get_volume_types_for_pool(
|
|
728
|
+
pool.to_dict())
|
|
729
|
+
self.assertEqual([], result)
|
|
730
|
+
|
|
731
|
+
def test_get_volume_types_for_pool_no_capabilities(
|
|
732
|
+
self, mock_cinder):
|
|
733
|
+
"""Test pool with no capabilities selects no-specs types."""
|
|
734
|
+
cinder_util = cinder_helper.CinderHelper()
|
|
735
|
+
pool = self.fake_pool(
|
|
736
|
+
name='host@backend#pool',
|
|
737
|
+
capabilities={}
|
|
738
|
+
)
|
|
739
|
+
volume_type1 = self.fake_volume_type(
|
|
740
|
+
name='type_no_specs',
|
|
741
|
+
extra_specs={}
|
|
742
|
+
)
|
|
743
|
+
volume_type2 = self.fake_volume_type(
|
|
744
|
+
name='type_with_specs',
|
|
745
|
+
extra_specs={'volume_backend_name': 'backend'}
|
|
746
|
+
)
|
|
747
|
+
cinder_util.cinder.volume_types.list.return_value = [
|
|
748
|
+
volume_type1, volume_type2
|
|
749
|
+
]
|
|
750
|
+
result = cinder_util.get_volume_types_for_pool(
|
|
751
|
+
pool.to_dict())
|
|
752
|
+
self.assertEqual(result, ['type_no_specs', 'type_with_specs'])
|
|
753
|
+
|
|
754
|
+
def test_get_volume_types_for_pool_requirements_not_in_caps(
|
|
755
|
+
self, mock_cinder):
|
|
756
|
+
"""Test volume type with requirements not in pool capabilities."""
|
|
757
|
+
cinder_util = cinder_helper.CinderHelper()
|
|
758
|
+
pool = self.fake_pool(
|
|
759
|
+
name='host@backend#pool',
|
|
760
|
+
capabilities={'volume_backend_name': 'backend'}
|
|
761
|
+
)
|
|
762
|
+
volume_type = self.fake_volume_type(
|
|
763
|
+
name='type_extra_reqs',
|
|
764
|
+
extra_specs={
|
|
765
|
+
'volume_backend_name': 'backend',
|
|
766
|
+
'disk_speed': 'fast',
|
|
767
|
+
}
|
|
768
|
+
)
|
|
769
|
+
cinder_util.cinder.volume_types.list.return_value = [
|
|
770
|
+
volume_type]
|
|
771
|
+
result = cinder_util.get_volume_types_for_pool(
|
|
772
|
+
pool.to_dict())
|
|
773
|
+
self.assertEqual(result, ['type_extra_reqs'])
|
|
774
|
+
|
|
775
|
+
def test_get_volume_types_for_pool_multiple_specs_partial_match(
|
|
776
|
+
self, mock_cinder):
|
|
777
|
+
"""Test volume type with multiple extra_specs partial match."""
|
|
778
|
+
cinder_util = cinder_helper.CinderHelper()
|
|
779
|
+
pool = self.fake_pool(
|
|
780
|
+
name='host@backend#pool',
|
|
781
|
+
capabilities={
|
|
782
|
+
'volume_backend_name': 'backend',
|
|
783
|
+
'disk_speed': 'fast',
|
|
784
|
+
'compression': 'enabled',
|
|
785
|
+
}
|
|
786
|
+
)
|
|
787
|
+
volume_type = self.fake_volume_type(
|
|
788
|
+
name='type_partial',
|
|
789
|
+
extra_specs={
|
|
790
|
+
'volume_backend_name': 'backend',
|
|
791
|
+
'disk_speed': 'slow',
|
|
792
|
+
'compression': 'enabled',
|
|
793
|
+
}
|
|
794
|
+
)
|
|
795
|
+
cinder_util.cinder.volume_types.list.return_value = [
|
|
796
|
+
volume_type]
|
|
797
|
+
result = cinder_util.get_volume_types_for_pool(
|
|
798
|
+
pool.to_dict())
|
|
799
|
+
self.assertEqual([], result)
|
|
@@ -104,3 +104,25 @@ class TestService(base.TestCase):
|
|
|
104
104
|
self.assertIsInstance(
|
|
105
105
|
dummy_service.conductor_topic_handler,
|
|
106
106
|
om.rpc.server.RPCServer)
|
|
107
|
+
|
|
108
|
+
@mock.patch.object(service.LOG, 'debug', autospec=True)
|
|
109
|
+
def test_transport_url_not_logged(self, mock_debug):
|
|
110
|
+
transport_url = 'rabbit://api:secret-pass@example.com:5672/'
|
|
111
|
+
CONF.set_override('transport_url', transport_url)
|
|
112
|
+
|
|
113
|
+
dummy_service = service.Service(DummyManager)
|
|
114
|
+
dummy_service.conductor_topic_handler = None
|
|
115
|
+
dummy_service.notification_handler = None
|
|
116
|
+
dummy_service.heartbeat = None
|
|
117
|
+
|
|
118
|
+
dummy_service.start()
|
|
119
|
+
dummy_service.stop()
|
|
120
|
+
|
|
121
|
+
logged = ' '.join(
|
|
122
|
+
str(arg) for call in mock_debug.call_args_list for arg in call.args
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
self.assertNotIn(transport_url, logged)
|
|
126
|
+
self.assertNotIn('secret-pass', logged)
|
|
127
|
+
mock_debug.assert_any_call("Connecting to messaging transport")
|
|
128
|
+
mock_debug.assert_any_call("Disconnecting from messaging transport")
|
|
@@ -66,18 +66,21 @@ class TestModel(base.TestCase):
|
|
|
66
66
|
|
|
67
67
|
@mock.patch.object(model_root.ModelRoot, 'get_all_compute_nodes')
|
|
68
68
|
@mock.patch.object(model_root.ModelRoot, 'get_node_instances')
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
fake_instance =
|
|
74
|
-
uuid='fake_instance_uuid'
|
|
75
|
-
fields=['uuid'])
|
|
69
|
+
@mock.patch('watcher.decision_engine.model.model_root.LOG')
|
|
70
|
+
def test_get_model_to_list(self, mock_log, mock_instances, mock_nodes):
|
|
71
|
+
fake_compute_node = element.ComputeNode(
|
|
72
|
+
uuid='fake_node_uuid', hostname="hostname0")
|
|
73
|
+
fake_instance = element.Instance(
|
|
74
|
+
uuid='fake_instance_uuid')
|
|
76
75
|
|
|
77
76
|
mock_nodes.return_value = {'fake_node_uuid': fake_compute_node}
|
|
78
77
|
mock_instances.return_value = [fake_instance]
|
|
79
78
|
|
|
80
|
-
expected_keys = [
|
|
79
|
+
expected_keys = [
|
|
80
|
+
'server_uuid', 'node_uuid', 'node_state', 'node_hostname',
|
|
81
|
+
'node_status', 'server_flavor_extra_specs', 'server_locked',
|
|
82
|
+
'server_pinned_az', 'server_state', 'server_watcher_exclude'
|
|
83
|
+
]
|
|
81
84
|
|
|
82
85
|
result = model_root.ModelRoot().to_list()
|
|
83
86
|
self.assertEqual(1, len(result))
|
|
@@ -85,10 +88,20 @@ class TestModel(base.TestCase):
|
|
|
85
88
|
result_keys = result[0].keys()
|
|
86
89
|
self.assertEqual(sorted(expected_keys), sorted(result_keys))
|
|
87
90
|
|
|
91
|
+
mock_log.debug.assert_any_call(
|
|
92
|
+
'Attribute %s for Instance: %s is not provided', 'name',
|
|
93
|
+
'fake_instance_uuid'
|
|
94
|
+
)
|
|
95
|
+
mock_log.debug.assert_any_call(
|
|
96
|
+
'Attribute %s for Compute: %s is not provided', 'memory',
|
|
97
|
+
'hostname0'
|
|
98
|
+
)
|
|
99
|
+
|
|
88
100
|
# test compute node has no instance
|
|
89
101
|
mock_instances.return_value = []
|
|
90
102
|
|
|
91
|
-
expected_keys = ['
|
|
103
|
+
expected_keys = ['node_hostname', 'node_status',
|
|
104
|
+
'node_state', 'node_uuid']
|
|
92
105
|
|
|
93
106
|
result = model_root.ModelRoot().to_list()
|
|
94
107
|
self.assertEqual(1, len(result))
|
|
@@ -96,6 +109,23 @@ class TestModel(base.TestCase):
|
|
|
96
109
|
result_keys = result[0].keys()
|
|
97
110
|
self.assertEqual(expected_keys, list(result_keys))
|
|
98
111
|
|
|
112
|
+
def test_model_to_list_all_fields(self):
|
|
113
|
+
struct_str = self.load_data('scenario_1.xml')
|
|
114
|
+
model = model_root.ModelRoot.from_xml(struct_str)
|
|
115
|
+
model_list = model.to_list()
|
|
116
|
+
self.assertIsInstance(model_list, list)
|
|
117
|
+
self.assertEqual(len(model_list), 8)
|
|
118
|
+
self.assertEqual(model_list[0]['server_uuid'], 'INSTANCE_0')
|
|
119
|
+
|
|
120
|
+
def test_model_to_xml_all_fields(self):
|
|
121
|
+
struct_str = self.load_data('scenario_2_with_metrics.xml')
|
|
122
|
+
|
|
123
|
+
model = model_root.ModelRoot.from_xml(struct_str)
|
|
124
|
+
model_xml = model.to_xml()
|
|
125
|
+
self.assertIsInstance(model_xml, str)
|
|
126
|
+
self.assertIn('Instance', model_xml)
|
|
127
|
+
self.assertIn('Compute', model_xml)
|
|
128
|
+
|
|
99
129
|
def test_get_node_by_instance_uuid(self):
|
|
100
130
|
model = model_root.ModelRoot()
|
|
101
131
|
uuid_ = "{0}".format(uuidutils.generate_uuid())
|
|
@@ -549,6 +549,30 @@ class TestZoneMigration(TestBaseStrategy):
|
|
|
549
549
|
global_efficacy_value = solution.global_efficacy[2].get('value', 0)
|
|
550
550
|
self.assertEqual(100, global_efficacy_value)
|
|
551
551
|
|
|
552
|
+
def test_execute_migrate_volume_no_dst_type(self):
|
|
553
|
+
volume_on_src1 = self.fake_volume(host="src1@back1#pool1",
|
|
554
|
+
id=volume_uuid_mapping["volume_1"],
|
|
555
|
+
name="volume_1")
|
|
556
|
+
self.m_c_helper.get_volume_list.return_value = [
|
|
557
|
+
volume_on_src1,
|
|
558
|
+
]
|
|
559
|
+
|
|
560
|
+
self.m_n_helper.get_instance_list.return_value = []
|
|
561
|
+
|
|
562
|
+
self.input_parameters["storage_pools"] = [
|
|
563
|
+
{"src_pool": "src1@back1#pool1",
|
|
564
|
+
"src_type": "type1", "dst_pool": "dst1@back1#pool1"},
|
|
565
|
+
]
|
|
566
|
+
|
|
567
|
+
solution = self.strategy.execute()
|
|
568
|
+
|
|
569
|
+
migration_types = collections.Counter(
|
|
570
|
+
[action.get('input_parameters')['migration_type']
|
|
571
|
+
for action in solution.actions])
|
|
572
|
+
self.assertEqual(1, migration_types.get("migrate", 0))
|
|
573
|
+
global_efficacy_value = solution.global_efficacy[2].get('value', 0)
|
|
574
|
+
self.assertEqual(100, global_efficacy_value)
|
|
575
|
+
|
|
552
576
|
def test_execute_migrate_volume_no_dst_pool(self):
|
|
553
577
|
volume_on_src1 = self.fake_volume(host="src1@back1#pool1",
|
|
554
578
|
id=volume_uuid_mapping["volume_1"],
|
|
@@ -1549,3 +1573,88 @@ class TestZoneMigration(TestBaseStrategy):
|
|
|
1549
1573
|
targets = self.strategy.filtered_targets()
|
|
1550
1574
|
self.assertEqual(targets.get("volume"),
|
|
1551
1575
|
[volume_on_src2, volume_on_src1])
|
|
1576
|
+
|
|
1577
|
+
def test_get_dst_pool_and_type_matching_src_pool_type(self):
|
|
1578
|
+
self.input_parameters["storage_pools"] = [
|
|
1579
|
+
{
|
|
1580
|
+
"src_pool": "src1@back1#pool1",
|
|
1581
|
+
"src_type": "type1",
|
|
1582
|
+
"dst_pool": "dst1@back1#pool1",
|
|
1583
|
+
},
|
|
1584
|
+
{
|
|
1585
|
+
"src_pool": "src1@back1#pool1",
|
|
1586
|
+
"src_type": "type2", "dst_type": "type3"
|
|
1587
|
+
}
|
|
1588
|
+
]
|
|
1589
|
+
pool = "src1@back1#pool1"
|
|
1590
|
+
src_type = "type1"
|
|
1591
|
+
dst_pool, dst_type = self.strategy.get_dst_pool_and_type(
|
|
1592
|
+
pool, src_type
|
|
1593
|
+
)
|
|
1594
|
+
self.assertEqual(dst_pool, "dst1@back1#pool1")
|
|
1595
|
+
self.assertIsNone(dst_type)
|
|
1596
|
+
|
|
1597
|
+
def test_get_dst_pool_and_type_matching_src_pool(self):
|
|
1598
|
+
self.input_parameters["storage_pools"] = [
|
|
1599
|
+
{
|
|
1600
|
+
"src_pool": "src1@back1#pool1",
|
|
1601
|
+
"src_type": "type1",
|
|
1602
|
+
"dst_pool": "dst1@back1#pool1",
|
|
1603
|
+
},
|
|
1604
|
+
{
|
|
1605
|
+
"src_pool": "src1@back1#pool1",
|
|
1606
|
+
"dst_pool": "dst2@back2#pool2"
|
|
1607
|
+
}
|
|
1608
|
+
]
|
|
1609
|
+
pool = "src1@back1#pool1"
|
|
1610
|
+
src_type = "type1"
|
|
1611
|
+
dst_pool, dst_type = self.strategy.get_dst_pool_and_type(
|
|
1612
|
+
pool, src_type
|
|
1613
|
+
)
|
|
1614
|
+
self.assertEqual(dst_pool, "dst1@back1#pool1")
|
|
1615
|
+
self.assertIsNone(dst_type)
|
|
1616
|
+
|
|
1617
|
+
# check that after not matching type it falls backs to matching
|
|
1618
|
+
# src_pool
|
|
1619
|
+
pool = "src1@back1#pool1"
|
|
1620
|
+
src_type = "typet"
|
|
1621
|
+
dst_pool, dst_type = self.strategy.get_dst_pool_and_type(
|
|
1622
|
+
pool, src_type
|
|
1623
|
+
)
|
|
1624
|
+
self.assertEqual(dst_pool, "dst2@back2#pool2")
|
|
1625
|
+
self.assertIsNone(dst_type)
|
|
1626
|
+
|
|
1627
|
+
def test_get_dst_pool_and_type_not_matching_pool(self):
|
|
1628
|
+
pool = "src3@back1#pool1"
|
|
1629
|
+
src_type = "type5"
|
|
1630
|
+
dst_pool, dst_type = self.strategy.get_dst_pool_and_type(
|
|
1631
|
+
pool, src_type
|
|
1632
|
+
)
|
|
1633
|
+
self.assertIsNone(dst_pool)
|
|
1634
|
+
self.assertIsNone(dst_type)
|
|
1635
|
+
|
|
1636
|
+
def test_get_dst_pool_and_type_same_src_pool_different_src_type(self):
|
|
1637
|
+
self.input_parameters["storage_pools"] = [
|
|
1638
|
+
{
|
|
1639
|
+
"src_pool": "src1@back1#pool1",
|
|
1640
|
+
"src_type": "type1",
|
|
1641
|
+
"dst_pool": "dst1@back1#pool1",
|
|
1642
|
+
},
|
|
1643
|
+
{
|
|
1644
|
+
"src_pool": "src1@back1#pool1",
|
|
1645
|
+
"src_type": "type2", "dst_type": "type3"
|
|
1646
|
+
}
|
|
1647
|
+
]
|
|
1648
|
+
pool = "src1@back1#pool1"
|
|
1649
|
+
src_type = "type2"
|
|
1650
|
+
dst_pool, dst_type = self.strategy.get_dst_pool_and_type(
|
|
1651
|
+
pool, src_type
|
|
1652
|
+
)
|
|
1653
|
+
self.assertIsNone(dst_pool)
|
|
1654
|
+
self.assertEqual(dst_type, "type3")
|
|
1655
|
+
src_type = "type1"
|
|
1656
|
+
dst_pool, dst_type = self.strategy.get_dst_pool_and_type(
|
|
1657
|
+
pool, src_type
|
|
1658
|
+
)
|
|
1659
|
+
self.assertEqual(dst_pool, "dst1@back1#pool1")
|
|
1660
|
+
self.assertIsNone(dst_type)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"git_version": "398f603b", "is_release": true}
|
|
File without changes
|
{python_watcher-15.1.0.data → python_watcher-15.1.1.data}/data/etc/watcher/README-watcher.conf.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|