Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1caf89686c | ||
|
|
ed3224835a | ||
|
|
8a7e316f73 | ||
|
|
112ac3bbdf | ||
|
|
d1ab697612 | ||
|
|
095ca0ffb2 | ||
|
|
e1131a65d8 | ||
|
|
5e507e56f4 | ||
|
|
62cb8a8d29 | ||
|
|
50e5e86c75 | ||
|
|
e3dd5c2a7e | ||
|
|
630c2cbb79 | ||
|
|
d49c6c16a6 | ||
|
|
e51e7e4317 | ||
|
|
244d28afa6 | ||
|
|
7ac1d0d048 | ||
|
|
52d701a56e | ||
|
|
ea1fd5967a | ||
|
|
547bf0529f | ||
|
|
659cbf3207 | ||
|
|
eb5a362287 | ||
|
|
198d827645 | ||
|
|
59c5adc8ad |
@@ -2,3 +2,4 @@
|
||||
host=review.openstack.org
|
||||
port=29418
|
||||
project=openstack/watcher.git
|
||||
defaultbranch=stable/ocata
|
||||
|
||||
@@ -188,7 +188,7 @@ function init_watcher {
|
||||
recreate_database watcher
|
||||
|
||||
# Create watcher schema
|
||||
$WATCHER_BIN_DIR/watcher-db-manage --config-file $WATCHER_CONF create_schema
|
||||
$WATCHER_BIN_DIR/watcher-db-manage --config-file $WATCHER_CONF upgrade head
|
||||
fi
|
||||
create_watcher_cache_dir
|
||||
}
|
||||
|
||||
54
doc/notification_samples/action_plan-create.json
Normal file
54
doc/notification_samples/action_plan-create.json
Normal file
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"publisher_id": "infra-optim:node0",
|
||||
"payload": {
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"strategy": {
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"display_name": "test strategy",
|
||||
"name": "TEST",
|
||||
"updated_at": null,
|
||||
"parameters_spec": {},
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"deleted_at": null
|
||||
},
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "StrategyPayload"
|
||||
},
|
||||
"created_at": null,
|
||||
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"audit": {
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"audit_type": "ONESHOT",
|
||||
"scope": [],
|
||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"parameters": {},
|
||||
"interval": null,
|
||||
"deleted_at": null,
|
||||
"state": "PENDING",
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"updated_at": null
|
||||
},
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "TerseAuditPayload"
|
||||
},
|
||||
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
|
||||
"global_efficacy": {},
|
||||
"deleted_at": null,
|
||||
"state": "RECOMMENDED",
|
||||
"updated_at": null
|
||||
},
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "ActionPlanCreatePayload"
|
||||
},
|
||||
"priority": "INFO",
|
||||
"message_id": "5148bff1-ea06-4ad6-8e4e-8c85ca5eb629",
|
||||
"event_type": "action_plan.create",
|
||||
"timestamp": "2016-10-18 09:52:05.219414"
|
||||
}
|
||||
54
doc/notification_samples/action_plan-delete.json
Normal file
54
doc/notification_samples/action_plan-delete.json
Normal file
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"publisher_id": "infra-optim:node0",
|
||||
"timestamp": "2016-10-18 09:52:05.219414",
|
||||
"payload": {
|
||||
"watcher_object.data": {
|
||||
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"audit": {
|
||||
"watcher_object.data": {
|
||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"interval": null,
|
||||
"audit_type": "ONESHOT",
|
||||
"scope": [],
|
||||
"updated_at": null,
|
||||
"deleted_at": null,
|
||||
"state": "PENDING",
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"parameters": {}
|
||||
},
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.name": "TerseAuditPayload",
|
||||
"watcher_object.namespace": "watcher"
|
||||
},
|
||||
"global_efficacy": {},
|
||||
"updated_at": null,
|
||||
"deleted_at": null,
|
||||
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"strategy": {
|
||||
"watcher_object.data": {
|
||||
"uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"name": "TEST",
|
||||
"display_name": "test strategy",
|
||||
"deleted_at": null,
|
||||
"updated_at": null,
|
||||
"parameters_spec": {}
|
||||
},
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.name": "StrategyPayload",
|
||||
"watcher_object.namespace": "watcher"
|
||||
},
|
||||
"state": "DELETED"
|
||||
},
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.name": "ActionPlanDeletePayload",
|
||||
"watcher_object.namespace": "watcher"
|
||||
},
|
||||
"event_type": "action_plan.delete",
|
||||
"message_id": "3d137686-a1fd-4683-ab40-c4210aac2140",
|
||||
"priority": "INFO"
|
||||
}
|
||||
55
doc/notification_samples/action_plan-execution-end.json
Normal file
55
doc/notification_samples/action_plan-execution-end.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"event_type": "action_plan.execution.end",
|
||||
"payload": {
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "ActionPlanActionPayload",
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"deleted_at": null,
|
||||
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"audit": {
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "TerseAuditPayload",
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"deleted_at": null,
|
||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"scope": [],
|
||||
"audit_type": "ONESHOT",
|
||||
"state": "SUCCEEDED",
|
||||
"parameters": {},
|
||||
"interval": null,
|
||||
"updated_at": null
|
||||
}
|
||||
},
|
||||
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
|
||||
"fault": null,
|
||||
"state": "ONGOING",
|
||||
"global_efficacy": {},
|
||||
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"strategy": {
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "StrategyPayload",
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"deleted_at": null,
|
||||
"name": "TEST",
|
||||
"uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"parameters_spec": {},
|
||||
"display_name": "test strategy",
|
||||
"updated_at": null
|
||||
}
|
||||
},
|
||||
"updated_at": null
|
||||
}
|
||||
},
|
||||
"priority": "INFO",
|
||||
"message_id": "3984dc2b-8aef-462b-a220-8ae04237a56e",
|
||||
"timestamp": "2016-10-18 09:52:05.219414",
|
||||
"publisher_id": "infra-optim:node0"
|
||||
}
|
||||
65
doc/notification_samples/action_plan-execution-error.json
Normal file
65
doc/notification_samples/action_plan-execution-error.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"event_type": "action_plan.execution.error",
|
||||
"publisher_id": "infra-optim:node0",
|
||||
"priority": "ERROR",
|
||||
"message_id": "9a45c5ae-0e21-4300-8fa0-5555d52a66d9",
|
||||
"payload": {
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "ActionPlanActionPayload",
|
||||
"watcher_object.data": {
|
||||
"fault": {
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "ExceptionPayload",
|
||||
"watcher_object.data": {
|
||||
"exception_message": "TEST",
|
||||
"module_name": "watcher.tests.notifications.test_action_plan_notification",
|
||||
"function_name": "test_send_action_plan_action_with_error",
|
||||
"exception": "WatcherException"
|
||||
}
|
||||
},
|
||||
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"strategy": {
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "StrategyPayload",
|
||||
"watcher_object.data": {
|
||||
"uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"name": "TEST",
|
||||
"updated_at": null,
|
||||
"display_name": "test strategy",
|
||||
"parameters_spec": {},
|
||||
"deleted_at": null
|
||||
}
|
||||
},
|
||||
"updated_at": null,
|
||||
"deleted_at": null,
|
||||
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"audit": {
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "TerseAuditPayload",
|
||||
"watcher_object.data": {
|
||||
"parameters": {},
|
||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"scope": [],
|
||||
"updated_at": null,
|
||||
"audit_type": "ONESHOT",
|
||||
"interval": null,
|
||||
"deleted_at": null,
|
||||
"state": "PENDING"
|
||||
}
|
||||
},
|
||||
"global_efficacy": {},
|
||||
"state": "ONGOING"
|
||||
}
|
||||
},
|
||||
"timestamp": "2016-10-18 09:52:05.219414"
|
||||
}
|
||||
55
doc/notification_samples/action_plan-execution-start.json
Normal file
55
doc/notification_samples/action_plan-execution-start.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"event_type": "action_plan.execution.start",
|
||||
"payload": {
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "ActionPlanActionPayload",
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"deleted_at": null,
|
||||
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"audit": {
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "TerseAuditPayload",
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"deleted_at": null,
|
||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"scope": [],
|
||||
"audit_type": "ONESHOT",
|
||||
"state": "PENDING",
|
||||
"parameters": {},
|
||||
"interval": null,
|
||||
"updated_at": null
|
||||
}
|
||||
},
|
||||
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
|
||||
"fault": null,
|
||||
"state": "ONGOING",
|
||||
"global_efficacy": {},
|
||||
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"strategy": {
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "StrategyPayload",
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"deleted_at": null,
|
||||
"name": "TEST",
|
||||
"uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"parameters_spec": {},
|
||||
"display_name": "test strategy",
|
||||
"updated_at": null
|
||||
}
|
||||
},
|
||||
"updated_at": null
|
||||
}
|
||||
},
|
||||
"priority": "INFO",
|
||||
"message_id": "3984dc2b-8aef-462b-a220-8ae04237a56e",
|
||||
"timestamp": "2016-10-18 09:52:05.219414",
|
||||
"publisher_id": "infra-optim:node0"
|
||||
}
|
||||
63
doc/notification_samples/action_plan-update.json
Normal file
63
doc/notification_samples/action_plan-update.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"payload": {
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"audit": {
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"audit_type": "ONESHOT",
|
||||
"scope": [],
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"interval": null,
|
||||
"updated_at": null,
|
||||
"state": "PENDING",
|
||||
"deleted_at": null,
|
||||
"parameters": {}
|
||||
},
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "TerseAuditPayload"
|
||||
},
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
|
||||
"updated_at": null,
|
||||
"state_update": {
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"old_state": "PENDING",
|
||||
"state": "ONGOING"
|
||||
},
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "ActionPlanStateUpdatePayload"
|
||||
},
|
||||
"state": "ONGOING",
|
||||
"deleted_at": null,
|
||||
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"strategy": {
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"name": "TEST",
|
||||
"uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"display_name": "test strategy",
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"updated_at": null,
|
||||
"deleted_at": null,
|
||||
"parameters_spec": {}
|
||||
},
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "StrategyPayload"
|
||||
},
|
||||
"global_efficacy": {}
|
||||
},
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "ActionPlanUpdatePayload"
|
||||
},
|
||||
"publisher_id": "infra-optim:node0",
|
||||
"priority": "INFO",
|
||||
"timestamp": "2016-10-18 09:52:05.219414",
|
||||
"event_type": "action_plan.update",
|
||||
"message_id": "0a8a7329-fd5a-4ec6-97d7-2b776ce51a4c"
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
"state": "PENDING",
|
||||
"updated_at": null,
|
||||
"deleted_at": null,
|
||||
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"goal": {
|
||||
"watcher_object.data": {
|
||||
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
@@ -26,6 +27,7 @@
|
||||
},
|
||||
"interval": null,
|
||||
"scope": [],
|
||||
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"strategy": {
|
||||
"watcher_object.data": {
|
||||
"parameters_spec": {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"state": "DELETED",
|
||||
"updated_at": null,
|
||||
"deleted_at": null,
|
||||
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"goal": {
|
||||
"watcher_object.data": {
|
||||
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
@@ -26,6 +27,7 @@
|
||||
},
|
||||
"interval": null,
|
||||
"scope": [],
|
||||
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"strategy": {
|
||||
"watcher_object.data": {
|
||||
"parameters_spec": {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"updated_at": null,
|
||||
"deleted_at": null,
|
||||
"fault": null,
|
||||
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"goal": {
|
||||
"watcher_object.data": {
|
||||
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
@@ -27,6 +28,7 @@
|
||||
},
|
||||
"interval": null,
|
||||
"scope": [],
|
||||
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"strategy": {
|
||||
"watcher_object.data": {
|
||||
"parameters_spec": {
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0"
|
||||
},
|
||||
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"goal": {
|
||||
"watcher_object.data": {
|
||||
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
@@ -37,6 +38,7 @@
|
||||
},
|
||||
"interval": null,
|
||||
"scope": [],
|
||||
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"strategy": {
|
||||
"watcher_object.data": {
|
||||
"parameters_spec": {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"updated_at": null,
|
||||
"deleted_at": null,
|
||||
"fault": null,
|
||||
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"goal": {
|
||||
"watcher_object.data": {
|
||||
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
@@ -27,6 +28,7 @@
|
||||
},
|
||||
"interval": null,
|
||||
"scope": [],
|
||||
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"strategy": {
|
||||
"watcher_object.data": {
|
||||
"parameters_spec": {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"updated_at": null,
|
||||
"deleted_at": null,
|
||||
"fault": null,
|
||||
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"goal": {
|
||||
"watcher_object.data": {
|
||||
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
@@ -27,6 +28,7 @@
|
||||
},
|
||||
"interval": null,
|
||||
"scope": [],
|
||||
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"strategy": {
|
||||
"watcher_object.data": {
|
||||
"parameters_spec": {
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0"
|
||||
},
|
||||
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"goal": {
|
||||
"watcher_object.data": {
|
||||
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
@@ -37,6 +38,7 @@
|
||||
},
|
||||
"interval": null,
|
||||
"scope": [],
|
||||
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"strategy": {
|
||||
"watcher_object.data": {
|
||||
"parameters_spec": {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"updated_at": null,
|
||||
"deleted_at": null,
|
||||
"fault": null,
|
||||
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"goal": {
|
||||
"watcher_object.data": {
|
||||
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
@@ -27,6 +28,7 @@
|
||||
},
|
||||
"interval": null,
|
||||
"scope": [],
|
||||
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"strategy": {
|
||||
"watcher_object.data": {
|
||||
"parameters_spec": {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"payload": {
|
||||
"watcher_object.name": "AuditUpdatePayload",
|
||||
"watcher_object.data": {
|
||||
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"strategy": {
|
||||
"watcher_object.name": "StrategyPayload",
|
||||
"watcher_object.data": {
|
||||
@@ -36,6 +37,7 @@
|
||||
"scope": [],
|
||||
"created_at": "2016-11-04T16:51:21Z",
|
||||
"uuid": "f1e0d912-afd9-4bf2-91ef-c99cd08cc1ef",
|
||||
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"goal": {
|
||||
"watcher_object.name": "GoalPayload",
|
||||
"watcher_object.data": {
|
||||
|
||||
@@ -21,7 +21,7 @@ Overview
|
||||
Below you will find a diagram, showing the main components of Watcher:
|
||||
|
||||
.. image:: ./images/architecture.svg
|
||||
:width: 100%
|
||||
:width: 110%
|
||||
|
||||
|
||||
.. _components_definition:
|
||||
@@ -37,13 +37,12 @@ AMQP Bus
|
||||
The AMQP message bus handles internal asynchronous communications between the
|
||||
different Watcher components.
|
||||
|
||||
.. _cluster_history_db_definition:
|
||||
.. _cluster_datasource_definition:
|
||||
|
||||
Cluster History Database
|
||||
------------------------
|
||||
Datasource
|
||||
----------
|
||||
|
||||
This component stores the data related to the
|
||||
:ref:`Cluster History <cluster_history_definition>`.
|
||||
This component stores the metrics related to the cluster.
|
||||
|
||||
It can potentially rely on any appropriate storage system (InfluxDB, OpenTSDB,
|
||||
MongoDB,...) but will probably be more performant when using
|
||||
@@ -51,14 +50,6 @@ MongoDB,...) but will probably be more performant when using
|
||||
which are optimized for handling time series data, which are arrays of numbers
|
||||
indexed by time (a datetime or a datetime range).
|
||||
|
||||
.. _cluster_model_db_definition:
|
||||
|
||||
Cluster Model Database
|
||||
------------------------
|
||||
|
||||
This component stores the data related to the
|
||||
:ref:`Cluster Data Model <cluster_data_model_definition>`.
|
||||
|
||||
.. _archi_watcher_api_definition:
|
||||
|
||||
Watcher API
|
||||
@@ -193,8 +184,8 @@ data:
|
||||
:ref:`Managed resources <managed_resource_definition>` (e.g., the data stored
|
||||
in the Nova database). These models gives a strategy the ability to reason on
|
||||
the current state of a given :ref:`cluster <cluster_definition>`.
|
||||
- The data stored in the :ref:`Cluster History Database
|
||||
<cluster_history_db_definition>` which provides information about the past of
|
||||
- The data stored in the :ref:`Cluster Datasource
|
||||
<cluster_datasource_definition>` which provides information about the past of
|
||||
the :ref:`Cluster <cluster_definition>`.
|
||||
|
||||
Here below is a sequence diagram showing how the Decision Engine builds and
|
||||
@@ -452,6 +443,10 @@ state may be one of the following:
|
||||
- **CANCELLED** : the :ref:`Action Plan <action_plan_definition>` was in
|
||||
**RECOMMENDED**, **PENDING** or **ONGOING** state and was cancelled by the
|
||||
:ref:`Administrator <administrator_definition>`
|
||||
- **SUPERSEDED** : the :ref:`Action Plan <action_plan_definition>` was in
|
||||
RECOMMENDED state and was automatically superseded by Watcher, due to an
|
||||
expiration delay or an update of the
|
||||
:ref:`Cluster data model <cluster_data_model_definition>`
|
||||
|
||||
|
||||
The following diagram shows the different possible states of an
|
||||
|
||||
@@ -271,57 +271,44 @@ requires new metrics not covered by Ceilometer, you can add them through a
|
||||
.. _`Ceilometer plugin`: http://docs.openstack.org/developer/ceilometer/plugins.html
|
||||
.. _`Ceilosca`: https://github.com/openstack/monasca-ceilometer/blob/master/ceilosca/ceilometer/storage/impl_monasca.py
|
||||
|
||||
Read usage metrics using the Watcher Datasource Helper
|
||||
------------------------------------------------------
|
||||
|
||||
Read usage metrics using the Python binding
|
||||
-------------------------------------------
|
||||
|
||||
You can find the information about the Ceilometer Python binding on the
|
||||
OpenStack `ceilometer client python API documentation
|
||||
<http://docs.openstack.org/developer/python-ceilometerclient/api.html>`_
|
||||
|
||||
To facilitate the process, Watcher provides the ``osc`` attribute to every
|
||||
strategy which includes clients to major OpenStack services, including
|
||||
Ceilometer. So to access it within your strategy, you can do the following:
|
||||
The following code snippet shows how to invoke a Datasource Helper class:
|
||||
|
||||
.. code-block:: py
|
||||
|
||||
# Within your strategy "execute()"
|
||||
cclient = self.osc.ceilometer
|
||||
# TODO: Do something here
|
||||
from watcher.datasource import ceilometer as ceil
|
||||
from watcher.datasource import monasca as mon
|
||||
|
||||
@property
|
||||
def ceilometer(self):
|
||||
if self._ceilometer is None:
|
||||
self._ceilometer = ceil.CeilometerHelper(osc=self.osc)
|
||||
return self._ceilometer
|
||||
|
||||
@property
|
||||
def monasca(self):
|
||||
if self._monasca is None:
|
||||
self._monasca = mon.MonascaHelper(osc=self.osc)
|
||||
return self._monasca
|
||||
|
||||
Using that you can now query the values for that specific metric:
|
||||
|
||||
.. code-block:: py
|
||||
|
||||
query = None # e.g. [{'field': 'foo', 'op': 'le', 'value': 34},]
|
||||
value_cpu = cclient.samples.list(
|
||||
meter_name='cpu_util',
|
||||
limit=10, q=query)
|
||||
|
||||
|
||||
Read usage metrics using the Watcher Cluster History Helper
|
||||
-----------------------------------------------------------
|
||||
|
||||
Here below is the abstract ``BaseClusterHistory`` class of the Helper.
|
||||
|
||||
.. autoclass:: watcher.decision_engine.cluster.history.base.BaseClusterHistory
|
||||
:members:
|
||||
:noindex:
|
||||
|
||||
The following code snippet shows how to create a Cluster History class:
|
||||
|
||||
.. code-block:: py
|
||||
|
||||
from watcher.decision_engine.cluster.history import ceilometer as ceil
|
||||
|
||||
query_history = ceil.CeilometerClusterHistory()
|
||||
|
||||
Using that you can now query the values for that specific metric:
|
||||
|
||||
.. code-block:: py
|
||||
|
||||
query_history.statistic_aggregation(resource_id=compute_node.uuid,
|
||||
meter_name='compute.node.cpu.percent',
|
||||
period="7200",
|
||||
aggregate='avg'
|
||||
)
|
||||
if self.config.datasource == "ceilometer":
|
||||
resource_id = "%s_%s" % (node.uuid, node.hostname)
|
||||
return self.ceilometer.statistic_aggregation(
|
||||
resource_id=resource_id,
|
||||
meter_name='compute.node.cpu.percent',
|
||||
period="7200",
|
||||
aggregate='avg',
|
||||
)
|
||||
elif self.config.datasource == "monasca":
|
||||
statistics = self.monasca.statistic_aggregation(
|
||||
meter_name='compute.node.cpu.percent',
|
||||
dimensions=dict(hostname=node.uuid),
|
||||
period=7200,
|
||||
aggregate='avg'
|
||||
)
|
||||
|
||||
@@ -101,12 +101,6 @@ Cluster Data Model (CDM)
|
||||
|
||||
.. watcher-term:: watcher.decision_engine.model.collector.base
|
||||
|
||||
.. _cluster_history_definition:
|
||||
|
||||
Cluster History
|
||||
===============
|
||||
|
||||
.. watcher-term:: watcher.decision_engine.cluster.history.base
|
||||
|
||||
.. _controller_node_definition:
|
||||
|
||||
|
||||
BIN
doc/source/image_src/plantuml/action_plan_state_machine.png
Normal file
BIN
doc/source/image_src/plantuml/action_plan_state_machine.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
@@ -9,8 +9,10 @@ FAILED --> DELETED : Administrator removes\nAction Plan
|
||||
SUCCEEDED --> DELETED : Administrator removes\nAction Plan
|
||||
ONGOING --> CANCELLED : Administrator cancels\nAction Plan
|
||||
RECOMMENDED --> CANCELLED : Administrator cancels\nAction Plan
|
||||
RECOMMENDED --> SUPERSEDED : The Watcher Decision Engine supersedes\nAction Plan
|
||||
PENDING --> CANCELLED : Administrator cancels\nAction Plan
|
||||
CANCELLED --> DELETED
|
||||
SUPERSEDED --> DELETED
|
||||
DELETED --> [*]
|
||||
|
||||
@enduml
|
||||
|
||||
BIN
doc/source/image_src/plantuml/watcher_db_schema_diagram.png
Normal file
BIN
doc/source/image_src/plantuml/watcher_db_schema_diagram.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
@@ -60,6 +60,7 @@ table(audits) {
|
||||
interval : Integer, nullable
|
||||
parameters : JSONEncodedDict, nullable
|
||||
scope : JSONEncodedList, nullable
|
||||
auto_trigger: Boolean
|
||||
|
||||
created_at : DateTime
|
||||
updated_at : DateTime
|
||||
@@ -73,7 +74,6 @@ table(action_plans) {
|
||||
foreign_key("audit_id : Integer, nullable")
|
||||
foreign_key("strategy_id : Integer")
|
||||
uuid : String[36]
|
||||
first_action_id : Integer
|
||||
state : String[20], nullable
|
||||
global_efficacy : JSONEncodedDict, nullable
|
||||
|
||||
@@ -91,7 +91,7 @@ table(actions) {
|
||||
action_type : String[255]
|
||||
input_parameters : JSONEncodedDict, nullable
|
||||
state : String[20], nullable
|
||||
next : String[36], nullable
|
||||
parents : JSONEncodedList, nullable
|
||||
|
||||
created_at : DateTime
|
||||
updated_at : DateTime
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 58 KiB |
@@ -0,0 +1,3 @@
|
||||
---
|
||||
features:
|
||||
- Add notifications related to Action plan object.
|
||||
2
tox.ini
2
tox.ini
@@ -6,7 +6,7 @@ skipsdist = True
|
||||
[testenv]
|
||||
usedevelop = True
|
||||
whitelist_externals = find
|
||||
install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
|
||||
install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/ocata} {opts} {packages}
|
||||
setenv =
|
||||
VIRTUAL_ENV={envdir}
|
||||
deps = -r{toxinidir}/test-requirements.txt
|
||||
|
||||
@@ -455,7 +455,8 @@ class ActionPlansController(rest.RestController):
|
||||
:param action_plan_uuid: UUID of a action.
|
||||
"""
|
||||
context = pecan.request.context
|
||||
action_plan = api_utils.get_resource('ActionPlan', action_plan_uuid)
|
||||
action_plan = api_utils.get_resource(
|
||||
'ActionPlan', action_plan_uuid, eager=True)
|
||||
policy.enforce(context, 'action_plan:delete', action_plan,
|
||||
action='action_plan:delete')
|
||||
|
||||
@@ -474,8 +475,8 @@ class ActionPlansController(rest.RestController):
|
||||
raise exception.OperationNotPermitted
|
||||
|
||||
context = pecan.request.context
|
||||
action_plan_to_update = api_utils.get_resource('ActionPlan',
|
||||
action_plan_uuid)
|
||||
action_plan_to_update = api_utils.get_resource(
|
||||
'ActionPlan', action_plan_uuid, eager=True)
|
||||
policy.enforce(context, 'action_plan:update', action_plan_to_update,
|
||||
action='action_plan:update')
|
||||
|
||||
|
||||
@@ -50,6 +50,21 @@ from watcher.decision_engine import rpcapi
|
||||
from watcher import objects
|
||||
|
||||
|
||||
ALLOWED_AUDIT_TRANSITIONS = {
|
||||
objects.audit.State.PENDING:
|
||||
[objects.audit.State.ONGOING, objects.audit.State.CANCELLED],
|
||||
objects.audit.State.ONGOING:
|
||||
[objects.audit.State.FAILED, objects.audit.State.SUCCEEDED,
|
||||
objects.audit.State.CANCELLED],
|
||||
objects.audit.State.FAILED:
|
||||
[objects.audit.State.DELETED],
|
||||
objects.audit.State.SUCCEEDED:
|
||||
[objects.audit.State.DELETED],
|
||||
objects.audit.State.CANCELLED:
|
||||
[objects.audit.State.DELETED]
|
||||
}
|
||||
|
||||
|
||||
class AuditPostType(wtypes.Base):
|
||||
|
||||
audit_template_uuid = wtypes.wsattr(types.uuid, mandatory=False)
|
||||
@@ -561,6 +576,17 @@ class AuditsController(rest.RestController):
|
||||
except api_utils.JSONPATCH_EXCEPTIONS as e:
|
||||
raise exception.PatchError(patch=patch, reason=e)
|
||||
|
||||
initial_state = audit_dict['state']
|
||||
new_state = api_utils.get_patch_value(patch, 'state')
|
||||
allowed_states = ALLOWED_AUDIT_TRANSITIONS.get(initial_state, [])
|
||||
if new_state is not None and new_state not in allowed_states:
|
||||
error_message = _("State transition not allowed: "
|
||||
"(%(initial_state)s -> %(new_state)s)")
|
||||
raise exception.PatchError(
|
||||
patch=patch,
|
||||
reason=error_message % dict(
|
||||
initial_state=initial_state, new_state=new_state))
|
||||
|
||||
# Update only the fields that have changed
|
||||
for field in objects.Audit.fields:
|
||||
try:
|
||||
|
||||
@@ -333,6 +333,7 @@ class AuditTemplate(base.APIBase):
|
||||
|
||||
self.fields.append('goal_id')
|
||||
self.fields.append('strategy_id')
|
||||
setattr(self, 'strategy_id', kwargs.get('strategy_id', wtypes.Unset))
|
||||
|
||||
# goal_uuid & strategy_uuid are not part of
|
||||
# objects.AuditTemplate.fields because they're API-only attributes.
|
||||
|
||||
@@ -73,6 +73,12 @@ def apply_jsonpatch(doc, patch):
|
||||
return jsonpatch.apply_patch(doc, jsonpatch.JsonPatch(patch))
|
||||
|
||||
|
||||
def get_patch_value(patch, key):
|
||||
for p in patch:
|
||||
if p['op'] == 'replace' and p['path'] == '/%s' % key:
|
||||
return p['value']
|
||||
|
||||
|
||||
def as_filters_dict(**filters):
|
||||
filters_dict = {}
|
||||
for filter_name, filter_value in filters.items():
|
||||
|
||||
@@ -20,32 +20,47 @@ from oslo_log import log
|
||||
|
||||
from watcher.applier.action_plan import base
|
||||
from watcher.applier import default
|
||||
from watcher import notifications
|
||||
from watcher import objects
|
||||
from watcher.objects import fields
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class DefaultActionPlanHandler(base.BaseActionPlanHandler):
|
||||
|
||||
def __init__(self, context, service, action_plan_uuid):
|
||||
super(DefaultActionPlanHandler, self).__init__()
|
||||
self.ctx = context
|
||||
self.service = service
|
||||
self.action_plan_uuid = action_plan_uuid
|
||||
|
||||
def update_action_plan(self, uuid, state):
|
||||
action_plan = objects.ActionPlan.get_by_uuid(self.ctx, uuid)
|
||||
action_plan.state = state
|
||||
action_plan.save()
|
||||
|
||||
def execute(self):
|
||||
try:
|
||||
self.update_action_plan(self.action_plan_uuid,
|
||||
objects.action_plan.State.ONGOING)
|
||||
action_plan = objects.ActionPlan.get_by_uuid(
|
||||
self.ctx, self.action_plan_uuid, eager=True)
|
||||
action_plan.state = objects.action_plan.State.ONGOING
|
||||
action_plan.save()
|
||||
notifications.action_plan.send_action_notification(
|
||||
self.ctx, action_plan,
|
||||
action=fields.NotificationAction.EXECUTION,
|
||||
phase=fields.NotificationPhase.START)
|
||||
|
||||
applier = default.DefaultApplier(self.ctx, self.service)
|
||||
applier.execute(self.action_plan_uuid)
|
||||
state = objects.action_plan.State.SUCCEEDED
|
||||
|
||||
action_plan.state = objects.action_plan.State.SUCCEEDED
|
||||
notifications.action_plan.send_action_notification(
|
||||
self.ctx, action_plan,
|
||||
action=fields.NotificationAction.EXECUTION,
|
||||
phase=fields.NotificationPhase.END)
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
state = objects.action_plan.State.FAILED
|
||||
action_plan.state = objects.action_plan.State.FAILED
|
||||
notifications.action_plan.send_action_notification(
|
||||
self.ctx, action_plan,
|
||||
action=fields.NotificationAction.EXECUTION,
|
||||
priority=fields.NotificationPriority.ERROR,
|
||||
phase=fields.NotificationPhase.ERROR)
|
||||
finally:
|
||||
self.update_action_plan(self.action_plan_uuid, state)
|
||||
action_plan.save()
|
||||
|
||||
@@ -56,7 +56,7 @@ class DefaultApplier(base.BaseApplier):
|
||||
|
||||
def execute(self, action_plan_uuid):
|
||||
LOG.debug("Executing action plan %s ", action_plan_uuid)
|
||||
|
||||
filters = {'action_plan_uuid': action_plan_uuid}
|
||||
actions = objects.Action.list(self.context,
|
||||
filters=filters)
|
||||
actions = objects.Action.list(self.context, filters=filters)
|
||||
return self.engine.execute(actions)
|
||||
|
||||
@@ -71,7 +71,7 @@ def add_command_parsers(subparsers):
|
||||
"Optionally, use --revision to specify an alembic revision "
|
||||
"string to upgrade to.")
|
||||
parser.set_defaults(func=DBCommand.upgrade)
|
||||
parser.add_argument('--revision', nargs='?')
|
||||
parser.add_argument('revision', nargs='?')
|
||||
|
||||
parser = subparsers.add_parser(
|
||||
'downgrade',
|
||||
@@ -79,10 +79,10 @@ def add_command_parsers(subparsers):
|
||||
"While optional, one should generally use --revision to "
|
||||
"specify the alembic revision string to downgrade to.")
|
||||
parser.set_defaults(func=DBCommand.downgrade)
|
||||
parser.add_argument('--revision', nargs='?')
|
||||
parser.add_argument('revision', nargs='?')
|
||||
|
||||
parser = subparsers.add_parser('stamp')
|
||||
parser.add_argument('--revision', nargs='?')
|
||||
parser.add_argument('revision', nargs='?')
|
||||
parser.set_defaults(func=DBCommand.stamp)
|
||||
|
||||
parser = subparsers.add_parser(
|
||||
|
||||
@@ -75,7 +75,7 @@ class RequestContext(context.RequestContext):
|
||||
self.domain_name = domain_name
|
||||
self.domain_id = domain_id
|
||||
self.auth_token_info = auth_token_info
|
||||
self.user_id = user_id
|
||||
self.user_id = user_id or user
|
||||
self.project_id = project_id
|
||||
if not timestamp:
|
||||
timestamp = timeutils.utcnow()
|
||||
|
||||
@@ -174,6 +174,14 @@ class EagerlyLoadedAuditRequired(InvalidAudit):
|
||||
msg_fmt = _("Audit %(audit)s was not eagerly loaded")
|
||||
|
||||
|
||||
class InvalidActionPlan(Invalid):
|
||||
msg_fmt = _("Action plan %(action_plan)s is invalid")
|
||||
|
||||
|
||||
class EagerlyLoadedActionPlanRequired(InvalidActionPlan):
|
||||
msg_fmt = _("Action plan %(action_plan)s was not eagerly loaded")
|
||||
|
||||
|
||||
class InvalidUUID(Invalid):
|
||||
msg_fmt = _("Expected a uuid but received %(uuid)s")
|
||||
|
||||
@@ -402,11 +410,15 @@ class WildcardCharacterIsUsed(WatcherException):
|
||||
|
||||
# Model
|
||||
|
||||
class InstanceNotFound(WatcherException):
|
||||
class ComputeResourceNotFound(WatcherException):
|
||||
msg_fmt = _("The compute resource '%(name)s' could not be found")
|
||||
|
||||
|
||||
class InstanceNotFound(ComputeResourceNotFound):
|
||||
msg_fmt = _("The instance '%(name)s' could not be found")
|
||||
|
||||
|
||||
class ComputeNodeNotFound(WatcherException):
|
||||
class ComputeNodeNotFound(ComputeResourceNotFound):
|
||||
msg_fmt = _("The compute node %(name)s could not be found")
|
||||
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
Please see https://alembic.readthedocs.org/en/latest/index.html for general documentation
|
||||
|
||||
To create alembic migrations use:
|
||||
$ watcher-db-manage revision --message "description of revision" --autogenerate
|
||||
|
||||
Stamp db with most recent migration version, without actually running migrations
|
||||
$ watcher-db-manage stamp head
|
||||
|
||||
Upgrade can be performed by:
|
||||
$ watcher-db-manage upgrade
|
||||
$ watcher-db-manage upgrade head
|
||||
|
||||
Downgrading db:
|
||||
$ watcher-db-manage downgrade
|
||||
$ watcher-db-manage downgrade base
|
||||
62
watcher/db/sqlalchemy/alembic/README.rst
Normal file
62
watcher/db/sqlalchemy/alembic/README.rst
Normal file
@@ -0,0 +1,62 @@
|
||||
The migrations in the alembic/versions contain the changes needed to migrate
|
||||
from older Watcher releases to newer versions. A migration occurs by executing
|
||||
a script that details the changes needed to upgrade/downgrade the database. The
|
||||
migration scripts are ordered so that multiple scripts can run sequentially to
|
||||
update the database. The scripts are executed by Watcher's migration wrapper
|
||||
which uses the Alembic library to manage the migration. Watcher supports
|
||||
migration from Ocata or later.
|
||||
|
||||
|
||||
If you are a deployer or developer and want to migrate from Ocata to later
|
||||
release you must first add version tracking to the database::
|
||||
|
||||
$ watcher-db-manage --config-file /path/to/watcher.conf stamp ocata
|
||||
|
||||
|
||||
You can upgrade to the latest database version via::
|
||||
|
||||
$ watcher-db-manage --config-file /path/to/watcher.conf upgrade head
|
||||
|
||||
|
||||
To check the current database version::
|
||||
|
||||
$ watcher-db-manage --config-file /path/to/watcher.conf current
|
||||
|
||||
|
||||
To create a script to run the migration offline::
|
||||
|
||||
$ watcher-db-manage --config-file /path/to/watcher.conf upgrade head --sql
|
||||
|
||||
|
||||
To run the offline migration between specific migration versions::
|
||||
|
||||
$ watcher-db-manage --config-file /path/to/watcher.conf upgrade \
|
||||
<start version>:<end version> --sql
|
||||
|
||||
|
||||
Upgrade the database incrementally::
|
||||
|
||||
$ watcher-db-manage --config-file /path/to/watcher.conf upgrade --delta \
|
||||
<# of revs>
|
||||
|
||||
|
||||
Downgrade the database by a certain number of revisions::
|
||||
|
||||
$ watcher-db-manage --config-file /path/to/watcher.conf downgrade --delta \
|
||||
<# of revs>
|
||||
|
||||
|
||||
Create new revision::
|
||||
|
||||
$ watcher-db-manage --config-file /path/to/watcher.conf revision \
|
||||
-m "description of revision" --autogenerate
|
||||
|
||||
|
||||
Create a blank file::
|
||||
|
||||
$ watcher-db-manage --config-file /path/to/watcher.conf revision \
|
||||
-m "description of revision"
|
||||
|
||||
Please see https://alembic.readthedocs.org/en/latest/index.html for general
|
||||
documentation
|
||||
|
||||
203
watcher/db/sqlalchemy/alembic/versions/001_ocata.py
Normal file
203
watcher/db/sqlalchemy/alembic/versions/001_ocata.py
Normal file
@@ -0,0 +1,203 @@
|
||||
"""ocata release
|
||||
|
||||
Revision ID: 9894235b4278
|
||||
Revises: None
|
||||
Create Date: 2017-02-01 09:40:05.065981
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import oslo_db
|
||||
import sqlalchemy as sa
|
||||
from watcher.db.sqlalchemy import models
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '001'
|
||||
down_revision = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table(
|
||||
'goals',
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted', oslo_db.sqlalchemy.types.SoftDeleteInteger(),
|
||||
nullable=True),
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('uuid', sa.String(length=36), nullable=True),
|
||||
sa.Column('name', sa.String(length=63), nullable=False),
|
||||
sa.Column('display_name', sa.String(length=63), nullable=False),
|
||||
sa.Column('efficacy_specification', models.JSONEncodedList(),
|
||||
nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('name', 'deleted', name='uniq_goals0name'),
|
||||
sa.UniqueConstraint('uuid', name='uniq_goals0uuid')
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
'scoring_engines',
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted', oslo_db.sqlalchemy.types.SoftDeleteInteger(),
|
||||
nullable=True),
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('uuid', sa.String(length=36), nullable=False),
|
||||
sa.Column('name', sa.String(length=63), nullable=False),
|
||||
sa.Column('description', sa.String(length=255), nullable=True),
|
||||
sa.Column('metainfo', sa.Text(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('name', 'deleted',
|
||||
name='uniq_scoring_engines0name'),
|
||||
sa.UniqueConstraint('uuid', name='uniq_scoring_engines0uuid')
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
'services',
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted', oslo_db.sqlalchemy.types.SoftDeleteInteger(),
|
||||
nullable=True),
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(length=255), nullable=False),
|
||||
sa.Column('host', sa.String(length=255), nullable=False),
|
||||
sa.Column('last_seen_up', sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('host', 'name', 'deleted',
|
||||
name='uniq_services0host0name0deleted')
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
'strategies',
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted', oslo_db.sqlalchemy.types.SoftDeleteInteger(),
|
||||
nullable=True),
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('uuid', sa.String(length=36), nullable=True),
|
||||
sa.Column('name', sa.String(length=63), nullable=False),
|
||||
sa.Column('display_name', sa.String(length=63), nullable=False),
|
||||
sa.Column('goal_id', sa.Integer(), nullable=False),
|
||||
sa.Column('parameters_spec', models.JSONEncodedDict(),
|
||||
nullable=True),
|
||||
sa.ForeignKeyConstraint(['goal_id'], ['goals.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('name', 'deleted', name='uniq_strategies0name'),
|
||||
sa.UniqueConstraint('uuid', name='uniq_strategies0uuid')
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
'audit_templates',
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted', oslo_db.sqlalchemy.types.SoftDeleteInteger(),
|
||||
nullable=True),
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('uuid', sa.String(length=36), nullable=True),
|
||||
sa.Column('name', sa.String(length=63), nullable=True),
|
||||
sa.Column('description', sa.String(length=255), nullable=True),
|
||||
sa.Column('goal_id', sa.Integer(), nullable=False),
|
||||
sa.Column('strategy_id', sa.Integer(), nullable=True),
|
||||
sa.Column('scope', models.JSONEncodedList(),
|
||||
nullable=True),
|
||||
sa.ForeignKeyConstraint(['goal_id'], ['goals.id'], ),
|
||||
sa.ForeignKeyConstraint(['strategy_id'], ['strategies.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('name', 'deleted',
|
||||
name='uniq_audit_templates0name'),
|
||||
sa.UniqueConstraint('uuid', name='uniq_audit_templates0uuid')
|
||||
)
|
||||
op.create_table(
|
||||
'audits',
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted', oslo_db.sqlalchemy.types.SoftDeleteInteger(),
|
||||
nullable=True),
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('uuid', sa.String(length=36), nullable=True),
|
||||
sa.Column('audit_type', sa.String(length=20), nullable=True),
|
||||
sa.Column('state', sa.String(length=20), nullable=True),
|
||||
sa.Column('parameters', models.JSONEncodedDict(), nullable=True),
|
||||
sa.Column('interval', sa.Integer(), nullable=True),
|
||||
sa.Column('goal_id', sa.Integer(), nullable=False),
|
||||
sa.Column('strategy_id', sa.Integer(), nullable=True),
|
||||
sa.Column('scope', models.JSONEncodedList(), nullable=True),
|
||||
sa.Column('auto_trigger', sa.Boolean(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['goal_id'], ['goals.id'], ),
|
||||
sa.ForeignKeyConstraint(['strategy_id'], ['strategies.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('uuid', name='uniq_audits0uuid')
|
||||
)
|
||||
op.create_table(
|
||||
'action_plans',
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted', oslo_db.sqlalchemy.types.SoftDeleteInteger(),
|
||||
nullable=True),
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('uuid', sa.String(length=36), nullable=True),
|
||||
sa.Column('audit_id', sa.Integer(), nullable=False),
|
||||
sa.Column('strategy_id', sa.Integer(), nullable=False),
|
||||
sa.Column('state', sa.String(length=20), nullable=True),
|
||||
sa.Column('global_efficacy', models.JSONEncodedDict(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['audit_id'], ['audits.id'], ),
|
||||
sa.ForeignKeyConstraint(['strategy_id'], ['strategies.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('uuid', name='uniq_action_plans0uuid')
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
'actions',
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted', oslo_db.sqlalchemy.types.SoftDeleteInteger(),
|
||||
nullable=True),
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('uuid', sa.String(length=36), nullable=False),
|
||||
sa.Column('action_plan_id', sa.Integer(), nullable=False),
|
||||
sa.Column('action_type', sa.String(length=255), nullable=False),
|
||||
sa.Column('input_parameters', models.JSONEncodedDict(), nullable=True),
|
||||
sa.Column('state', sa.String(length=20), nullable=True),
|
||||
sa.Column('parents', models.JSONEncodedList(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['action_plan_id'], ['action_plans.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('uuid', name='uniq_actions0uuid')
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
'efficacy_indicators',
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted', oslo_db.sqlalchemy.types.SoftDeleteInteger(),
|
||||
nullable=True),
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('uuid', sa.String(length=36), nullable=True),
|
||||
sa.Column('name', sa.String(length=63), nullable=True),
|
||||
sa.Column('description', sa.String(length=255), nullable=True),
|
||||
sa.Column('unit', sa.String(length=63), nullable=True),
|
||||
sa.Column('value', sa.Numeric(), nullable=True),
|
||||
sa.Column('action_plan_id', sa.Integer(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['action_plan_id'], ['action_plans.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('uuid', name='uniq_efficacy_indicators0uuid')
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_table('efficacy_indicators')
|
||||
op.drop_table('actions')
|
||||
op.drop_table('action_plans')
|
||||
op.drop_table('audits')
|
||||
op.drop_table('audit_templates')
|
||||
op.drop_table('strategies')
|
||||
op.drop_table('services')
|
||||
op.drop_table('scoring_engines')
|
||||
op.drop_table('goals')
|
||||
@@ -54,7 +54,7 @@ def upgrade(revision, config=None):
|
||||
revision = revision or 'head'
|
||||
config = config or _alembic_config()
|
||||
|
||||
alembic.command.upgrade(config, revision or 'head')
|
||||
alembic.command.upgrade(config, revision)
|
||||
|
||||
|
||||
def create_schema(config=None, engine=None):
|
||||
|
||||
@@ -129,7 +129,7 @@ class AuditHandler(BaseAuditHandler):
|
||||
solution = self.do_execute(audit, request_context)
|
||||
self.post_execute(audit, solution, request_context)
|
||||
except exception.ActionPlanIsOngoing as e:
|
||||
LOG.exception(e)
|
||||
LOG.warning(e)
|
||||
if audit.audit_type == objects.audit.AuditType.ONESHOT.value:
|
||||
self.update_audit_state(audit, objects.audit.State.CANCELLED)
|
||||
except Exception as e:
|
||||
|
||||
@@ -215,8 +215,7 @@ class ModelBuilder(object):
|
||||
compute_node = self.model.get_node_by_uuid(
|
||||
cnode_uuid)
|
||||
# Connect the instance to its compute node
|
||||
self.model.add_edge(
|
||||
instance, compute_node, label='RUNS_ON')
|
||||
self.model.map_instance(instance, compute_node)
|
||||
except exception.ComputeNodeNotFound:
|
||||
continue
|
||||
|
||||
|
||||
@@ -30,7 +30,8 @@ LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class Element(base.WatcherObject, base.WatcherObjectDictCompat):
|
||||
class Element(base.WatcherObject, base.WatcherObjectDictCompat,
|
||||
base.WatcherComparableObject):
|
||||
|
||||
# Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
@@ -57,13 +57,13 @@ class ModelRoot(nx.DiGraph, base.Model):
|
||||
@lockutils.synchronized("model_root")
|
||||
def add_node(self, node):
|
||||
self.assert_node(node)
|
||||
super(ModelRoot, self).add_node(node)
|
||||
super(ModelRoot, self).add_node(node.uuid, node)
|
||||
|
||||
@lockutils.synchronized("model_root")
|
||||
def remove_node(self, node):
|
||||
self.assert_node(node)
|
||||
try:
|
||||
super(ModelRoot, self).remove_node(node)
|
||||
super(ModelRoot, self).remove_node(node.uuid)
|
||||
except nx.NetworkXError as exc:
|
||||
LOG.exception(exc)
|
||||
raise exception.ComputeNodeNotFound(name=node.uuid)
|
||||
@@ -72,7 +72,7 @@ class ModelRoot(nx.DiGraph, base.Model):
|
||||
def add_instance(self, instance):
|
||||
self.assert_instance(instance)
|
||||
try:
|
||||
super(ModelRoot, self).add_node(instance)
|
||||
super(ModelRoot, self).add_node(instance.uuid, instance)
|
||||
except nx.NetworkXError as exc:
|
||||
LOG.exception(exc)
|
||||
raise exception.InstanceNotFound(name=instance.uuid)
|
||||
@@ -80,7 +80,7 @@ class ModelRoot(nx.DiGraph, base.Model):
|
||||
@lockutils.synchronized("model_root")
|
||||
def remove_instance(self, instance):
|
||||
self.assert_instance(instance)
|
||||
super(ModelRoot, self).remove_node(instance)
|
||||
super(ModelRoot, self).remove_node(instance.uuid)
|
||||
|
||||
@lockutils.synchronized("model_root")
|
||||
def map_instance(self, instance, node):
|
||||
@@ -98,7 +98,7 @@ class ModelRoot(nx.DiGraph, base.Model):
|
||||
self.assert_node(node)
|
||||
self.assert_instance(instance)
|
||||
|
||||
self.add_edge(instance, node)
|
||||
self.add_edge(instance.uuid, node.uuid)
|
||||
|
||||
@lockutils.synchronized("model_root")
|
||||
def unmap_instance(self, instance, node):
|
||||
@@ -107,7 +107,7 @@ class ModelRoot(nx.DiGraph, base.Model):
|
||||
if isinstance(node, six.string_types):
|
||||
node = self.get_node_by_uuid(node)
|
||||
|
||||
self.remove_edge(instance, node)
|
||||
self.remove_edge(instance.uuid, node.uuid)
|
||||
|
||||
def delete_instance(self, instance, node=None):
|
||||
self.assert_instance(instance)
|
||||
@@ -130,55 +130,59 @@ class ModelRoot(nx.DiGraph, base.Model):
|
||||
return False
|
||||
|
||||
# unmap
|
||||
self.remove_edge(instance, source_node)
|
||||
self.remove_edge(instance.uuid, source_node.uuid)
|
||||
# map
|
||||
self.add_edge(instance, destination_node)
|
||||
self.add_edge(instance.uuid, destination_node.uuid)
|
||||
return True
|
||||
|
||||
@lockutils.synchronized("model_root")
|
||||
def get_all_compute_nodes(self):
|
||||
return {cn.uuid: cn for cn in self.nodes()
|
||||
return {uuid: cn for uuid, cn in self.nodes(data=True)
|
||||
if isinstance(cn, element.ComputeNode)}
|
||||
|
||||
@lockutils.synchronized("model_root")
|
||||
def get_node_by_uuid(self, uuid):
|
||||
for graph_node in self.nodes():
|
||||
if (isinstance(graph_node, element.ComputeNode) and
|
||||
graph_node.uuid == uuid):
|
||||
return graph_node
|
||||
raise exception.ComputeNodeNotFound(name=uuid)
|
||||
try:
|
||||
return self._get_by_uuid(uuid)
|
||||
except exception.ComputeResourceNotFound:
|
||||
raise exception.ComputeNodeNotFound(name=uuid)
|
||||
|
||||
@lockutils.synchronized("model_root")
|
||||
def get_instance_by_uuid(self, uuid):
|
||||
return self._get_instance_by_uuid(uuid)
|
||||
try:
|
||||
return self._get_by_uuid(uuid)
|
||||
except exception.ComputeResourceNotFound:
|
||||
raise exception.InstanceNotFound(name=uuid)
|
||||
|
||||
def _get_instance_by_uuid(self, uuid):
|
||||
for graph_node in self.nodes():
|
||||
if (isinstance(graph_node, element.Instance) and
|
||||
graph_node.uuid == str(uuid)):
|
||||
return graph_node
|
||||
raise exception.InstanceNotFound(name=uuid)
|
||||
def _get_by_uuid(self, uuid):
|
||||
try:
|
||||
return self.node[uuid]
|
||||
except Exception as exc:
|
||||
LOG.exception(exc)
|
||||
raise exception.ComputeResourceNotFound(name=uuid)
|
||||
|
||||
@lockutils.synchronized("model_root")
|
||||
def get_node_by_instance_uuid(self, instance_uuid):
|
||||
instance = self._get_instance_by_uuid(instance_uuid)
|
||||
for node in self.neighbors(instance):
|
||||
instance = self._get_by_uuid(instance_uuid)
|
||||
for node_uuid in self.neighbors(instance.uuid):
|
||||
node = self._get_by_uuid(node_uuid)
|
||||
if isinstance(node, element.ComputeNode):
|
||||
return node
|
||||
raise exception.ComputeNodeNotFound(name=instance_uuid)
|
||||
|
||||
@lockutils.synchronized("model_root")
|
||||
def get_all_instances(self):
|
||||
return {inst.uuid: inst for inst in self.nodes()
|
||||
return {uuid: inst for uuid, inst in self.nodes(data=True)
|
||||
if isinstance(inst, element.Instance)}
|
||||
|
||||
@lockutils.synchronized("model_root")
|
||||
def get_node_instances(self, node):
|
||||
self.assert_node(node)
|
||||
node_instances = []
|
||||
for neighbor in self.predecessors(node):
|
||||
if isinstance(neighbor, element.Instance):
|
||||
node_instances.append(neighbor)
|
||||
for instance_uuid in self.predecessors(node.uuid):
|
||||
instance = self._get_by_uuid(instance_uuid)
|
||||
if isinstance(instance, element.Instance):
|
||||
node_instances.append(instance)
|
||||
|
||||
return node_instances
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ class WeightPlanner(base.BasePlanner):
|
||||
*Limitations*
|
||||
|
||||
- This planner requires to have action_weights and parallelization configs
|
||||
tuned well.
|
||||
tuned well.
|
||||
"""
|
||||
|
||||
def __init__(self, config):
|
||||
|
||||
@@ -169,7 +169,7 @@ class DefaultScope(base.BaseScope):
|
||||
try:
|
||||
node_name = cluster_model.get_node_by_instance_uuid(
|
||||
instance_uuid).uuid
|
||||
except exception.InstanceNotFound:
|
||||
except exception.ComputeResourceNotFound:
|
||||
LOG.warning(_LW("The following instance %s cannot be found. "
|
||||
"It might be deleted from CDM along with node"
|
||||
" instance was hosted on."),
|
||||
|
||||
@@ -415,11 +415,11 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
||||
sorted_score):
|
||||
number_migrations = 0
|
||||
for mig_instance, __ in sorted_instances:
|
||||
for j in range(0, len(sorted_score)):
|
||||
for node_uuid, __ in sorted_score:
|
||||
mig_source_node = self.compute_model.get_node_by_uuid(
|
||||
node_to_release)
|
||||
mig_destination_node = self.compute_model.get_node_by_uuid(
|
||||
sorted_score[j][0])
|
||||
node_uuid)
|
||||
|
||||
result = self.check_migration(
|
||||
mig_source_node, mig_destination_node, mig_instance)
|
||||
|
||||
@@ -350,7 +350,7 @@ class Syncer(object):
|
||||
for strategy_id, synced_strategy in self.strategy_mapping.items():
|
||||
filters = {"strategy_id": strategy_id}
|
||||
stale_action_plans = objects.ActionPlan.list(
|
||||
self.ctx, filters=filters)
|
||||
self.ctx, filters=filters, eager=True)
|
||||
|
||||
# Update strategy IDs for all stale action plans (w/o saving)
|
||||
for action_plan in stale_action_plans:
|
||||
@@ -369,7 +369,7 @@ class Syncer(object):
|
||||
for audit_id, synced_audit in self.stale_audits_map.items():
|
||||
filters = {"audit_id": audit_id}
|
||||
stale_action_plans = objects.ActionPlan.list(
|
||||
self.ctx, filters=filters)
|
||||
self.ctx, filters=filters, eager=True)
|
||||
|
||||
# Update audit IDs for all stale action plans (w/o saving)
|
||||
for action_plan in stale_action_plans:
|
||||
@@ -448,7 +448,7 @@ class Syncer(object):
|
||||
audit.id].state = objects.audit.State.CANCELLED
|
||||
|
||||
stale_action_plans = objects.ActionPlan.list(
|
||||
self.ctx, filters=filters)
|
||||
self.ctx, filters=filters, eager=True)
|
||||
for action_plan in stale_action_plans:
|
||||
LOG.warning(
|
||||
_LW("Action Plan '%(action_plan)s' references a "
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
# need to be changed after we moved these function inside the package
|
||||
# Todo(gibi): remove these imports after legacy notifications using these are
|
||||
# transformed to versioned notifications
|
||||
from watcher.notifications import action_plan # noqa
|
||||
from watcher.notifications import audit # noqa
|
||||
from watcher.notifications import exception # noqa
|
||||
from watcher.notifications import goal # noqa
|
||||
|
||||
312
watcher/notifications/action_plan.py
Normal file
312
watcher/notifications/action_plan.py
Normal file
@@ -0,0 +1,312 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2017 b<>com
|
||||
#
|
||||
# Authors: Vincent FRANCOISE <vincent.francoise@b-com.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from watcher.common import context as wcontext
|
||||
from watcher.common import exception
|
||||
from watcher.notifications import audit as audit_notifications
|
||||
from watcher.notifications import base as notificationbase
|
||||
from watcher.notifications import exception as exception_notifications
|
||||
from watcher.notifications import strategy as strategy_notifications
|
||||
from watcher import objects
|
||||
from watcher.objects import base
|
||||
from watcher.objects import fields as wfields
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
@base.WatcherObjectRegistry.register_notification
|
||||
class ActionPlanPayload(notificationbase.NotificationPayloadBase):
|
||||
SCHEMA = {
|
||||
'uuid': ('action_plan', 'uuid'),
|
||||
|
||||
'state': ('action_plan', 'state'),
|
||||
'global_efficacy': ('action_plan', 'global_efficacy'),
|
||||
'audit_uuid': ('audit', 'uuid'),
|
||||
'strategy_uuid': ('strategy', 'uuid'),
|
||||
|
||||
'created_at': ('action_plan', 'created_at'),
|
||||
'updated_at': ('action_plan', 'updated_at'),
|
||||
'deleted_at': ('action_plan', 'deleted_at'),
|
||||
}
|
||||
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
fields = {
|
||||
'uuid': wfields.UUIDField(),
|
||||
'state': wfields.StringField(),
|
||||
'global_efficacy': wfields.FlexibleDictField(nullable=True),
|
||||
'audit_uuid': wfields.UUIDField(),
|
||||
'strategy_uuid': wfields.UUIDField(),
|
||||
'audit': wfields.ObjectField('TerseAuditPayload'),
|
||||
'strategy': wfields.ObjectField('StrategyPayload'),
|
||||
|
||||
'created_at': wfields.DateTimeField(nullable=True),
|
||||
'updated_at': wfields.DateTimeField(nullable=True),
|
||||
'deleted_at': wfields.DateTimeField(nullable=True),
|
||||
}
|
||||
|
||||
def __init__(self, action_plan, audit, strategy, **kwargs):
|
||||
super(ActionPlanPayload, self).__init__(
|
||||
audit=audit, strategy=strategy, **kwargs)
|
||||
self.populate_schema(
|
||||
action_plan=action_plan, audit=audit, strategy=strategy)
|
||||
|
||||
|
||||
@base.WatcherObjectRegistry.register_notification
|
||||
class ActionPlanStateUpdatePayload(notificationbase.NotificationPayloadBase):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
fields = {
|
||||
'old_state': wfields.StringField(nullable=True),
|
||||
'state': wfields.StringField(nullable=True),
|
||||
}
|
||||
|
||||
|
||||
@base.WatcherObjectRegistry.register_notification
|
||||
class ActionPlanCreatePayload(ActionPlanPayload):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
fields = {}
|
||||
|
||||
def __init__(self, action_plan, audit, strategy):
|
||||
super(ActionPlanCreatePayload, self).__init__(
|
||||
action_plan=action_plan,
|
||||
audit=audit,
|
||||
strategy=strategy)
|
||||
|
||||
|
||||
@base.WatcherObjectRegistry.register_notification
|
||||
class ActionPlanUpdatePayload(ActionPlanPayload):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
fields = {
|
||||
'state_update': wfields.ObjectField('ActionPlanStateUpdatePayload'),
|
||||
}
|
||||
|
||||
def __init__(self, action_plan, state_update, audit, strategy):
|
||||
super(ActionPlanUpdatePayload, self).__init__(
|
||||
action_plan=action_plan,
|
||||
state_update=state_update,
|
||||
audit=audit,
|
||||
strategy=strategy)
|
||||
|
||||
|
||||
@base.WatcherObjectRegistry.register_notification
|
||||
class ActionPlanActionPayload(ActionPlanPayload):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
fields = {
|
||||
'fault': wfields.ObjectField('ExceptionPayload', nullable=True),
|
||||
}
|
||||
|
||||
def __init__(self, action_plan, audit, strategy, **kwargs):
|
||||
super(ActionPlanActionPayload, self).__init__(
|
||||
action_plan=action_plan,
|
||||
audit=audit,
|
||||
strategy=strategy,
|
||||
**kwargs)
|
||||
|
||||
|
||||
@base.WatcherObjectRegistry.register_notification
|
||||
class ActionPlanDeletePayload(ActionPlanPayload):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
fields = {}
|
||||
|
||||
def __init__(self, action_plan, audit, strategy):
|
||||
super(ActionPlanDeletePayload, self).__init__(
|
||||
action_plan=action_plan,
|
||||
audit=audit,
|
||||
strategy=strategy)
|
||||
|
||||
|
||||
@notificationbase.notification_sample('action_plan-execution-error.json')
|
||||
@notificationbase.notification_sample('action_plan-execution-end.json')
|
||||
@notificationbase.notification_sample('action_plan-execution-start.json')
|
||||
@base.WatcherObjectRegistry.register_notification
|
||||
class ActionPlanActionNotification(notificationbase.NotificationBase):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
fields = {
|
||||
'payload': wfields.ObjectField('ActionPlanActionPayload')
|
||||
}
|
||||
|
||||
|
||||
@notificationbase.notification_sample('action_plan-create.json')
|
||||
@base.WatcherObjectRegistry.register_notification
|
||||
class ActionPlanCreateNotification(notificationbase.NotificationBase):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
fields = {
|
||||
'payload': wfields.ObjectField('ActionPlanCreatePayload')
|
||||
}
|
||||
|
||||
|
||||
@notificationbase.notification_sample('action_plan-update.json')
|
||||
@base.WatcherObjectRegistry.register_notification
|
||||
class ActionPlanUpdateNotification(notificationbase.NotificationBase):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
fields = {
|
||||
'payload': wfields.ObjectField('ActionPlanUpdatePayload')
|
||||
}
|
||||
|
||||
|
||||
@notificationbase.notification_sample('action_plan-delete.json')
|
||||
@base.WatcherObjectRegistry.register_notification
|
||||
class ActionPlanDeleteNotification(notificationbase.NotificationBase):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
fields = {
|
||||
'payload': wfields.ObjectField('ActionPlanDeletePayload')
|
||||
}
|
||||
|
||||
|
||||
def _get_common_payload(action_plan):
|
||||
audit = None
|
||||
strategy = None
|
||||
try:
|
||||
audit = action_plan.audit
|
||||
strategy = action_plan.strategy
|
||||
except NotImplementedError:
|
||||
raise exception.EagerlyLoadedActionPlanRequired(
|
||||
action_plan=action_plan.uuid)
|
||||
|
||||
goal = objects.Goal.get(
|
||||
wcontext.make_context(show_deleted=True), audit.goal_id)
|
||||
audit_payload = audit_notifications.TerseAuditPayload(
|
||||
audit=audit, goal_uuid=goal.uuid)
|
||||
|
||||
strategy_payload = strategy_notifications.StrategyPayload(
|
||||
strategy=strategy)
|
||||
|
||||
return audit_payload, strategy_payload
|
||||
|
||||
|
||||
def send_create(context, action_plan, service='infra-optim', host=None):
|
||||
"""Emit an action_plan.create notification."""
|
||||
audit_payload, strategy_payload = _get_common_payload(action_plan)
|
||||
|
||||
versioned_payload = ActionPlanCreatePayload(
|
||||
action_plan=action_plan,
|
||||
audit=audit_payload,
|
||||
strategy=strategy_payload,
|
||||
)
|
||||
|
||||
notification = ActionPlanCreateNotification(
|
||||
priority=wfields.NotificationPriority.INFO,
|
||||
event_type=notificationbase.EventType(
|
||||
object='action_plan',
|
||||
action=wfields.NotificationAction.CREATE),
|
||||
publisher=notificationbase.NotificationPublisher(
|
||||
host=host or CONF.host,
|
||||
binary=service),
|
||||
payload=versioned_payload)
|
||||
|
||||
notification.emit(context)
|
||||
|
||||
|
||||
def send_update(context, action_plan, service='infra-optim',
|
||||
host=None, old_state=None):
|
||||
"""Emit an action_plan.update notification."""
|
||||
audit_payload, strategy_payload = _get_common_payload(action_plan)
|
||||
|
||||
state_update = ActionPlanStateUpdatePayload(
|
||||
old_state=old_state,
|
||||
state=action_plan.state if old_state else None)
|
||||
|
||||
versioned_payload = ActionPlanUpdatePayload(
|
||||
action_plan=action_plan,
|
||||
state_update=state_update,
|
||||
audit=audit_payload,
|
||||
strategy=strategy_payload,
|
||||
)
|
||||
|
||||
notification = ActionPlanUpdateNotification(
|
||||
priority=wfields.NotificationPriority.INFO,
|
||||
event_type=notificationbase.EventType(
|
||||
object='action_plan',
|
||||
action=wfields.NotificationAction.UPDATE),
|
||||
publisher=notificationbase.NotificationPublisher(
|
||||
host=host or CONF.host,
|
||||
binary=service),
|
||||
payload=versioned_payload)
|
||||
|
||||
notification.emit(context)
|
||||
|
||||
|
||||
def send_delete(context, action_plan, service='infra-optim', host=None):
|
||||
"""Emit an action_plan.delete notification."""
|
||||
audit_payload, strategy_payload = _get_common_payload(action_plan)
|
||||
|
||||
versioned_payload = ActionPlanDeletePayload(
|
||||
action_plan=action_plan,
|
||||
audit=audit_payload,
|
||||
strategy=strategy_payload,
|
||||
)
|
||||
|
||||
notification = ActionPlanDeleteNotification(
|
||||
priority=wfields.NotificationPriority.INFO,
|
||||
event_type=notificationbase.EventType(
|
||||
object='action_plan',
|
||||
action=wfields.NotificationAction.DELETE),
|
||||
publisher=notificationbase.NotificationPublisher(
|
||||
host=host or CONF.host,
|
||||
binary=service),
|
||||
payload=versioned_payload)
|
||||
|
||||
notification.emit(context)
|
||||
|
||||
|
||||
def send_action_notification(context, action_plan, action, phase=None,
|
||||
priority=wfields.NotificationPriority.INFO,
|
||||
service='infra-optim', host=None):
|
||||
"""Emit an action_plan action notification."""
|
||||
audit_payload, strategy_payload = _get_common_payload(action_plan)
|
||||
|
||||
fault = None
|
||||
if phase == wfields.NotificationPhase.ERROR:
|
||||
fault = exception_notifications.ExceptionPayload.from_exception()
|
||||
|
||||
versioned_payload = ActionPlanActionPayload(
|
||||
action_plan=action_plan,
|
||||
audit=audit_payload,
|
||||
strategy=strategy_payload,
|
||||
fault=fault,
|
||||
)
|
||||
|
||||
notification = ActionPlanActionNotification(
|
||||
priority=priority,
|
||||
event_type=notificationbase.EventType(
|
||||
object='action_plan',
|
||||
action=action,
|
||||
phase=phase),
|
||||
publisher=notificationbase.NotificationPublisher(
|
||||
host=host or CONF.host,
|
||||
binary=service),
|
||||
payload=versioned_payload)
|
||||
|
||||
notification.emit(context)
|
||||
@@ -30,7 +30,7 @@ CONF = cfg.CONF
|
||||
|
||||
|
||||
@base.WatcherObjectRegistry.register_notification
|
||||
class AuditPayload(notificationbase.NotificationPayloadBase):
|
||||
class TerseAuditPayload(notificationbase.NotificationPayloadBase):
|
||||
SCHEMA = {
|
||||
'uuid': ('audit', 'uuid'),
|
||||
|
||||
@@ -57,19 +57,54 @@ class AuditPayload(notificationbase.NotificationPayloadBase):
|
||||
'scope': wfields.FlexibleListOfDictField(nullable=True),
|
||||
'goal_uuid': wfields.UUIDField(),
|
||||
'strategy_uuid': wfields.UUIDField(nullable=True),
|
||||
'goal': wfields.ObjectField('GoalPayload'),
|
||||
'strategy': wfields.ObjectField('StrategyPayload', nullable=True),
|
||||
|
||||
'created_at': wfields.DateTimeField(nullable=True),
|
||||
'updated_at': wfields.DateTimeField(nullable=True),
|
||||
'deleted_at': wfields.DateTimeField(nullable=True),
|
||||
}
|
||||
|
||||
def __init__(self, audit, **kwargs):
|
||||
super(AuditPayload, self).__init__(**kwargs)
|
||||
def __init__(self, audit, goal_uuid, strategy_uuid=None, **kwargs):
|
||||
super(TerseAuditPayload, self).__init__(
|
||||
goal_uuid=goal_uuid, strategy_uuid=strategy_uuid, **kwargs)
|
||||
self.populate_schema(audit=audit)
|
||||
|
||||
|
||||
@base.WatcherObjectRegistry.register_notification
|
||||
class AuditPayload(TerseAuditPayload):
|
||||
SCHEMA = {
|
||||
'uuid': ('audit', 'uuid'),
|
||||
|
||||
'audit_type': ('audit', 'audit_type'),
|
||||
'state': ('audit', 'state'),
|
||||
'parameters': ('audit', 'parameters'),
|
||||
'interval': ('audit', 'interval'),
|
||||
'scope': ('audit', 'scope'),
|
||||
|
||||
'created_at': ('audit', 'created_at'),
|
||||
'updated_at': ('audit', 'updated_at'),
|
||||
'deleted_at': ('audit', 'deleted_at'),
|
||||
}
|
||||
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
fields = {
|
||||
'goal': wfields.ObjectField('GoalPayload'),
|
||||
'strategy': wfields.ObjectField('StrategyPayload', nullable=True),
|
||||
}
|
||||
|
||||
def __init__(self, audit, goal, strategy=None, **kwargs):
|
||||
if not kwargs.get('goal_uuid'):
|
||||
kwargs['goal_uuid'] = goal.uuid
|
||||
|
||||
if strategy and not kwargs.get('strategy_uuid'):
|
||||
kwargs['strategy_uuid'] = strategy.uuid
|
||||
|
||||
super(AuditPayload, self).__init__(
|
||||
audit=audit, goal=goal,
|
||||
strategy=strategy, **kwargs)
|
||||
|
||||
|
||||
@base.WatcherObjectRegistry.register_notification
|
||||
class AuditStateUpdatePayload(notificationbase.NotificationPayloadBase):
|
||||
# Version 1.0: Initial version
|
||||
@@ -91,6 +126,7 @@ class AuditCreatePayload(AuditPayload):
|
||||
super(AuditCreatePayload, self).__init__(
|
||||
audit=audit,
|
||||
goal=goal,
|
||||
goal_uuid=goal.uuid,
|
||||
strategy=strategy)
|
||||
|
||||
|
||||
@@ -107,6 +143,7 @@ class AuditUpdatePayload(AuditPayload):
|
||||
audit=audit,
|
||||
state_update=state_update,
|
||||
goal=goal,
|
||||
goal_uuid=goal.uuid,
|
||||
strategy=strategy)
|
||||
|
||||
|
||||
@@ -122,6 +159,7 @@ class AuditActionPayload(AuditPayload):
|
||||
super(AuditActionPayload, self).__init__(
|
||||
audit=audit,
|
||||
goal=goal,
|
||||
goal_uuid=goal.uuid,
|
||||
strategy=strategy,
|
||||
**kwargs)
|
||||
|
||||
@@ -136,6 +174,7 @@ class AuditDeletePayload(AuditPayload):
|
||||
super(AuditDeletePayload, self).__init__(
|
||||
audit=audit,
|
||||
goal=goal,
|
||||
goal_uuid=goal.uuid,
|
||||
strategy=strategy)
|
||||
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
# under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
|
||||
from watcher.common import exception
|
||||
from watcher.common import rpc
|
||||
@@ -20,6 +21,7 @@ from watcher.objects import base
|
||||
from watcher.objects import fields as wfields
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
# Definition of notification levels in increasing order of severity
|
||||
NOTIFY_LEVELS = {
|
||||
@@ -59,7 +61,8 @@ class EventType(NotificationObject):
|
||||
# Version 1.0: Initial version
|
||||
# Version 1.1: Added STRATEGY action in NotificationAction enum
|
||||
# Version 1.2: Added PLANNER action in NotificationAction enum
|
||||
VERSION = '1.2'
|
||||
# Version 1.3: Added EXECUTION action in NotificationAction enum
|
||||
VERSION = '1.3'
|
||||
|
||||
fields = {
|
||||
'object': wfields.StringField(),
|
||||
@@ -171,6 +174,7 @@ class NotificationBase(NotificationObject):
|
||||
def _emit(self, context, event_type, publisher_id, payload):
|
||||
notifier = rpc.get_notifier(publisher_id)
|
||||
notify = getattr(notifier, self.priority)
|
||||
LOG.debug("Emitting notification `%s`", event_type)
|
||||
notify(context, event_type=event_type, payload=payload)
|
||||
|
||||
def emit(self, context):
|
||||
|
||||
@@ -72,6 +72,7 @@ state may be one of the following:
|
||||
from watcher.common import exception
|
||||
from watcher.common import utils
|
||||
from watcher.db import api as db_api
|
||||
from watcher import notifications
|
||||
from watcher import objects
|
||||
from watcher.objects import base
|
||||
from watcher.objects import fields as wfields
|
||||
@@ -117,6 +118,39 @@ class ActionPlan(base.WatcherPersistentObject, base.WatcherObject,
|
||||
'strategy': (objects.Strategy, 'strategy_id'),
|
||||
}
|
||||
|
||||
# Proxified field so we can keep the previous value after an update
|
||||
_state = None
|
||||
_old_state = None
|
||||
|
||||
# NOTE(v-francoise): The way oslo.versionedobjects works is by using a
|
||||
# __new__ that will automatically create the attributes referenced in
|
||||
# fields. These attributes are properties that raise an exception if no
|
||||
# value has been assigned, which means that they store the actual field
|
||||
# value in an "_obj_%(field)s" attribute. So because we want to proxify a
|
||||
# value that is already proxified, we have to do what you see below.
|
||||
@property
|
||||
def _obj_state(self):
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def _obj_old_state(self):
|
||||
return self._old_state
|
||||
|
||||
@property
|
||||
def old_state(self):
|
||||
return self._old_state
|
||||
|
||||
@_obj_old_state.setter
|
||||
def _obj_old_state(self, value):
|
||||
self._old_state = value
|
||||
|
||||
@_obj_state.setter
|
||||
def _obj_state(self, value):
|
||||
if self._old_state is None and self._state is None:
|
||||
self._state = value
|
||||
else:
|
||||
self._old_state, self._state = self._state, value
|
||||
|
||||
@base.remotable_classmethod
|
||||
def get(cls, context, action_plan_id, eager=False):
|
||||
"""Find a action_plan based on its id or uuid and return a Action object.
|
||||
@@ -198,6 +232,11 @@ class ActionPlan(base.WatcherPersistentObject, base.WatcherObject,
|
||||
# notifications containing information about the related relationships
|
||||
self._from_db_object(self, db_action_plan, eager=True)
|
||||
|
||||
def _notify():
|
||||
notifications.action_plan.send_create(self._context, self)
|
||||
|
||||
_notify()
|
||||
|
||||
@base.remotable
|
||||
def destroy(self):
|
||||
"""Delete the action plan from the DB"""
|
||||
@@ -221,8 +260,16 @@ class ActionPlan(base.WatcherPersistentObject, base.WatcherObject,
|
||||
"""
|
||||
updates = self.obj_get_changes()
|
||||
db_obj = self.dbapi.update_action_plan(self.uuid, updates)
|
||||
obj = self._from_db_object(self, db_obj, eager=False)
|
||||
obj = self._from_db_object(
|
||||
self.__class__(self._context), db_obj, eager=False)
|
||||
self.obj_refresh(obj)
|
||||
|
||||
def _notify():
|
||||
notifications.action_plan.send_update(
|
||||
self._context, self, old_state=self.old_state)
|
||||
|
||||
_notify()
|
||||
|
||||
self.obj_reset_changes()
|
||||
|
||||
@base.remotable
|
||||
@@ -262,3 +309,8 @@ class ActionPlan(base.WatcherPersistentObject, base.WatcherObject,
|
||||
obj = self._from_db_object(
|
||||
self.__class__(self._context), db_obj, eager=False)
|
||||
self.obj_refresh(obj)
|
||||
|
||||
def _notify():
|
||||
notifications.action_plan.send_delete(self._context, self)
|
||||
|
||||
_notify()
|
||||
|
||||
@@ -92,6 +92,10 @@ class WatcherObjectDictCompat(ovo_base.VersionedObjectDictCompat):
|
||||
pass
|
||||
|
||||
|
||||
class WatcherComparableObject(ovo_base.ComparableVersionedObject):
|
||||
pass
|
||||
|
||||
|
||||
class WatcherPersistentObject(object):
|
||||
"""Mixin class for Persistent objects.
|
||||
|
||||
|
||||
@@ -128,8 +128,9 @@ class NotificationAction(BaseWatcherEnum):
|
||||
|
||||
STRATEGY = 'strategy'
|
||||
PLANNER = 'planner'
|
||||
EXECUTION = 'execution'
|
||||
|
||||
ALL = (CREATE, UPDATE, EXCEPTION, DELETE, STRATEGY, PLANNER)
|
||||
ALL = (CREATE, UPDATE, EXCEPTION, DELETE, STRATEGY, PLANNER, EXECUTION)
|
||||
|
||||
|
||||
class NotificationPriorityField(BaseEnumField):
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
# limitations under the License.
|
||||
|
||||
import datetime
|
||||
import itertools
|
||||
import mock
|
||||
|
||||
from oslo_config import cfg
|
||||
@@ -267,7 +268,7 @@ class TestPatch(api_base.FunctionalTest):
|
||||
test_time = datetime.datetime(2000, 1, 1, 0, 0)
|
||||
mock_utcnow.return_value = test_time
|
||||
|
||||
new_state = objects.audit.State.SUCCEEDED
|
||||
new_state = objects.audit.State.CANCELLED
|
||||
response = self.get_json('/audits/%s' % self.audit.uuid)
|
||||
self.assertNotEqual(new_state, response['state'])
|
||||
|
||||
@@ -343,6 +344,128 @@ class TestPatch(api_base.FunctionalTest):
|
||||
self.assertTrue(response.json['error_message'])
|
||||
|
||||
|
||||
ALLOWED_TRANSITIONS = [
|
||||
{"original_state": objects.audit.State.PENDING,
|
||||
"new_state": objects.audit.State.ONGOING},
|
||||
{"original_state": objects.audit.State.PENDING,
|
||||
"new_state": objects.audit.State.CANCELLED},
|
||||
{"original_state": objects.audit.State.ONGOING,
|
||||
"new_state": objects.audit.State.FAILED},
|
||||
{"original_state": objects.audit.State.ONGOING,
|
||||
"new_state": objects.audit.State.SUCCEEDED},
|
||||
{"original_state": objects.audit.State.ONGOING,
|
||||
"new_state": objects.audit.State.CANCELLED},
|
||||
{"original_state": objects.audit.State.FAILED,
|
||||
"new_state": objects.audit.State.DELETED},
|
||||
{"original_state": objects.audit.State.SUCCEEDED,
|
||||
"new_state": objects.audit.State.DELETED},
|
||||
{"original_state": objects.audit.State.CANCELLED,
|
||||
"new_state": objects.audit.State.DELETED},
|
||||
]
|
||||
|
||||
|
||||
class TestPatchStateTransitionDenied(api_base.FunctionalTest):
|
||||
|
||||
STATES = [
|
||||
ap_state for ap_state in objects.audit.State.__dict__
|
||||
if not ap_state.startswith("_")
|
||||
]
|
||||
|
||||
scenarios = [
|
||||
(
|
||||
"%s -> %s" % (original_state, new_state),
|
||||
{"original_state": original_state,
|
||||
"new_state": new_state},
|
||||
)
|
||||
for original_state, new_state
|
||||
in list(itertools.product(STATES, STATES))
|
||||
if original_state != new_state
|
||||
and {"original_state": original_state,
|
||||
"new_state": new_state} not in ALLOWED_TRANSITIONS
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
super(TestPatchStateTransitionDenied, self).setUp()
|
||||
obj_utils.create_test_goal(self.context)
|
||||
obj_utils.create_test_strategy(self.context)
|
||||
obj_utils.create_test_audit_template(self.context)
|
||||
self.audit = obj_utils.create_test_audit(self.context,
|
||||
state=self.original_state)
|
||||
p = mock.patch.object(db_api.BaseConnection, 'update_audit')
|
||||
self.mock_audit_update = p.start()
|
||||
self.mock_audit_update.side_effect = self._simulate_rpc_audit_update
|
||||
self.addCleanup(p.stop)
|
||||
|
||||
def _simulate_rpc_audit_update(self, audit):
|
||||
audit.save()
|
||||
return audit
|
||||
|
||||
def test_replace_denied(self):
|
||||
response = self.get_json('/audits/%s' % self.audit.uuid)
|
||||
self.assertNotEqual(self.new_state, response['state'])
|
||||
|
||||
response = self.patch_json(
|
||||
'/audits/%s' % self.audit.uuid,
|
||||
[{'path': '/state', 'value': self.new_state,
|
||||
'op': 'replace'}],
|
||||
expect_errors=True)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertEqual(400, response.status_code)
|
||||
self.assertTrue(response.json['error_message'])
|
||||
|
||||
response = self.get_json('/audits/%s' % self.audit.uuid)
|
||||
self.assertEqual(self.original_state, response['state'])
|
||||
|
||||
|
||||
class TestPatchStateTransitionOk(api_base.FunctionalTest):
|
||||
|
||||
scenarios = [
|
||||
(
|
||||
"%s -> %s" % (transition["original_state"],
|
||||
transition["new_state"]),
|
||||
transition
|
||||
)
|
||||
for transition in ALLOWED_TRANSITIONS
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
super(TestPatchStateTransitionOk, self).setUp()
|
||||
obj_utils.create_test_goal(self.context)
|
||||
obj_utils.create_test_strategy(self.context)
|
||||
obj_utils.create_test_audit_template(self.context)
|
||||
self.audit = obj_utils.create_test_audit(self.context,
|
||||
state=self.original_state)
|
||||
p = mock.patch.object(db_api.BaseConnection, 'update_audit')
|
||||
self.mock_audit_update = p.start()
|
||||
self.mock_audit_update.side_effect = self._simulate_rpc_audit_update
|
||||
self.addCleanup(p.stop)
|
||||
|
||||
def _simulate_rpc_audit_update(self, audit):
|
||||
audit.save()
|
||||
return audit
|
||||
|
||||
@mock.patch('oslo_utils.timeutils.utcnow')
|
||||
def test_replace_ok(self, mock_utcnow):
|
||||
test_time = datetime.datetime(2000, 1, 1, 0, 0)
|
||||
mock_utcnow.return_value = test_time
|
||||
|
||||
response = self.get_json('/audits/%s' % self.audit.uuid)
|
||||
self.assertNotEqual(self.new_state, response['state'])
|
||||
|
||||
response = self.patch_json(
|
||||
'/audits/%s' % self.audit.uuid,
|
||||
[{'path': '/state', 'value': self.new_state,
|
||||
'op': 'replace'}])
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
response = self.get_json('/audits/%s' % self.audit.uuid)
|
||||
self.assertEqual(self.new_state, response['state'])
|
||||
return_updated_at = timeutils.parse_isotime(
|
||||
response['updated_at']).replace(tzinfo=None)
|
||||
self.assertEqual(test_time, return_updated_at)
|
||||
|
||||
|
||||
class TestPost(api_base.FunctionalTest):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
import mock
|
||||
|
||||
from watcher.applier.action_plan import default
|
||||
from watcher.applier import default as ap_applier
|
||||
from watcher import notifications
|
||||
from watcher import objects
|
||||
from watcher.objects import action_plan as ap_objects
|
||||
from watcher.tests.db import base
|
||||
from watcher.tests.objects import utils as obj_utils
|
||||
@@ -25,17 +28,67 @@ from watcher.tests.objects import utils as obj_utils
|
||||
|
||||
class TestDefaultActionPlanHandler(base.DbTestCase):
|
||||
|
||||
class FakeApplierException(Exception):
|
||||
pass
|
||||
|
||||
def setUp(self):
|
||||
super(TestDefaultActionPlanHandler, self).setUp()
|
||||
|
||||
p_action_plan_notifications = mock.patch.object(
|
||||
notifications, 'action_plan', autospec=True)
|
||||
self.m_action_plan_notifications = p_action_plan_notifications.start()
|
||||
self.addCleanup(p_action_plan_notifications.stop)
|
||||
|
||||
obj_utils.create_test_goal(self.context)
|
||||
obj_utils.create_test_strategy(self.context)
|
||||
obj_utils.create_test_audit(self.context)
|
||||
self.action_plan = obj_utils.create_test_action_plan(self.context)
|
||||
|
||||
def test_launch_action_plan(self):
|
||||
@mock.patch.object(objects.ActionPlan, "get_by_uuid")
|
||||
def test_launch_action_plan(self, m_get_action_plan):
|
||||
m_get_action_plan.return_value = self.action_plan
|
||||
command = default.DefaultActionPlanHandler(
|
||||
self.context, mock.MagicMock(), self.action_plan.uuid)
|
||||
command.execute()
|
||||
action_plan = ap_objects.ActionPlan.get_by_uuid(
|
||||
self.context, self.action_plan.uuid)
|
||||
self.assertEqual(ap_objects.State.SUCCEEDED, action_plan.state)
|
||||
|
||||
expected_calls = [
|
||||
mock.call(self.context, self.action_plan,
|
||||
action=objects.fields.NotificationAction.EXECUTION,
|
||||
phase=objects.fields.NotificationPhase.START),
|
||||
mock.call(self.context, self.action_plan,
|
||||
action=objects.fields.NotificationAction.EXECUTION,
|
||||
phase=objects.fields.NotificationPhase.END)]
|
||||
|
||||
self.assertEqual(ap_objects.State.SUCCEEDED, self.action_plan.state)
|
||||
|
||||
self.assertEqual(
|
||||
expected_calls,
|
||||
self.m_action_plan_notifications
|
||||
.send_action_notification
|
||||
.call_args_list)
|
||||
|
||||
@mock.patch.object(ap_applier.DefaultApplier, "execute")
|
||||
@mock.patch.object(objects.ActionPlan, "get_by_uuid")
|
||||
def test_launch_action_plan_with_error(self, m_get_action_plan, m_execute):
|
||||
m_get_action_plan.return_value = self.action_plan
|
||||
m_execute.side_effect = self.FakeApplierException
|
||||
command = default.DefaultActionPlanHandler(
|
||||
self.context, mock.MagicMock(), self.action_plan.uuid)
|
||||
command.execute()
|
||||
|
||||
expected_calls = [
|
||||
mock.call(self.context, self.action_plan,
|
||||
action=objects.fields.NotificationAction.EXECUTION,
|
||||
phase=objects.fields.NotificationPhase.START),
|
||||
mock.call(self.context, self.action_plan,
|
||||
action=objects.fields.NotificationAction.EXECUTION,
|
||||
priority=objects.fields.NotificationPriority.ERROR,
|
||||
phase=objects.fields.NotificationPhase.ERROR)]
|
||||
|
||||
self.assertEqual(ap_objects.State.FAILED, self.action_plan.state)
|
||||
|
||||
self.assertEqual(
|
||||
expected_calls,
|
||||
self.m_action_plan_notifications
|
||||
.send_action_notification
|
||||
.call_args_list)
|
||||
|
||||
@@ -89,7 +89,7 @@ def get_test_audit(**kwargs):
|
||||
'updated_at': kwargs.get('updated_at'),
|
||||
'deleted_at': kwargs.get('deleted_at'),
|
||||
'parameters': kwargs.get('parameters', {}),
|
||||
'interval': kwargs.get('period', 3600),
|
||||
'interval': kwargs.get('interval', 3600),
|
||||
'goal_id': kwargs.get('goal_id', 1),
|
||||
'strategy_id': kwargs.get('strategy_id', None),
|
||||
'scope': kwargs.get('scope', []),
|
||||
|
||||
@@ -175,12 +175,19 @@ class TestAutoTriggerActionPlan(base.DbTestCase):
|
||||
self.ongoing_action_plan = obj_utils.create_test_action_plan(
|
||||
self.context,
|
||||
uuid=uuidutils.generate_uuid(),
|
||||
audit_id=self.audit.id)
|
||||
audit_id=self.audit.id,
|
||||
strategy_id=self.strategy.id,
|
||||
audit=self.audit,
|
||||
strategy=self.strategy,
|
||||
)
|
||||
self.recommended_action_plan = obj_utils.create_test_action_plan(
|
||||
self.context,
|
||||
uuid=uuidutils.generate_uuid(),
|
||||
state=objects.action_plan.State.ONGOING,
|
||||
audit_id=self.audit.id
|
||||
audit_id=self.audit.id,
|
||||
strategy_id=self.strategy.id,
|
||||
audit=self.audit,
|
||||
strategy=self.strategy,
|
||||
)
|
||||
|
||||
@mock.patch.object(objects.action_plan.ActionPlan, 'list')
|
||||
|
||||
@@ -153,7 +153,6 @@ class FakeMonascaMetrics(object):
|
||||
# measurements[uuid] = random.randint(1, 4)
|
||||
measurements[uuid] = 8
|
||||
|
||||
# import ipdb; ipdb.set_trace()
|
||||
return [{'columns': ['avg'],
|
||||
'statistics': [[float(measurements[str(uuid)])]]}]
|
||||
# return float(measurements[str(uuid)])
|
||||
|
||||
@@ -60,6 +60,7 @@ class TestActionScheduling(base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestActionScheduling, self).setUp()
|
||||
self.goal = db_utils.create_test_goal(name="dummy")
|
||||
self.strategy = db_utils.create_test_strategy(name="dummy")
|
||||
self.audit = db_utils.create_test_audit(
|
||||
uuid=utils.generate_uuid(), strategy_id=self.strategy.id)
|
||||
|
||||
@@ -61,6 +61,7 @@ class TestActionScheduling(base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestActionScheduling, self).setUp()
|
||||
self.goal = db_utils.create_test_goal(name="dummy")
|
||||
self.strategy = db_utils.create_test_strategy(name="dummy")
|
||||
self.audit = db_utils.create_test_audit(
|
||||
uuid=utils.generate_uuid(), strategy_id=self.strategy.id)
|
||||
|
||||
@@ -42,10 +42,8 @@ class TestDefaultScope(base.TestCase):
|
||||
for i in range(2)]
|
||||
model = default.DefaultScope(audit_scope,
|
||||
osc=mock.Mock()).get_scoped_model(cluster)
|
||||
|
||||
expected_edges = [('INSTANCE_2', 'Node_1')]
|
||||
edges = [(src.uuid, dst.uuid) for src, dst in model.edges()]
|
||||
self.assertEqual(sorted(expected_edges), sorted(edges))
|
||||
self.assertEqual(sorted(expected_edges), sorted(model.edges()))
|
||||
|
||||
@mock.patch.object(nova_helper.NovaHelper, 'get_availability_zone_list')
|
||||
def test_get_scoped_model_without_scope(self, mock_zone_list):
|
||||
@@ -67,8 +65,7 @@ class TestDefaultScope(base.TestCase):
|
||||
('INSTANCE_6', 'Node_3'),
|
||||
('INSTANCE_7', 'Node_4'),
|
||||
]
|
||||
edges = [(src.uuid, dst.uuid) for src, dst in model.edges()]
|
||||
self.assertEqual(sorted(expected_edges), sorted(edges))
|
||||
self.assertEqual(sorted(expected_edges), sorted(model.edges()))
|
||||
|
||||
@mock.patch.object(nova_helper.NovaHelper, 'get_aggregate_detail')
|
||||
@mock.patch.object(nova_helper.NovaHelper, 'get_aggregate_list')
|
||||
@@ -199,8 +196,7 @@ class TestDefaultScope(base.TestCase):
|
||||
('INSTANCE_1', 'Node_0'),
|
||||
('INSTANCE_6', 'Node_3'),
|
||||
('INSTANCE_7', 'Node_4')]
|
||||
edges = [(src.uuid, dst.uuid) for src, dst in model.edges()]
|
||||
self.assertEqual(sorted(expected_edges), sorted(edges))
|
||||
self.assertEqual(sorted(expected_edges), sorted(model.edges()))
|
||||
|
||||
def test_remove_instances_from_model(self):
|
||||
model = self.fake_cluster.generate_scenario_1()
|
||||
@@ -213,5 +209,4 @@ class TestDefaultScope(base.TestCase):
|
||||
('INSTANCE_5', 'Node_2'),
|
||||
('INSTANCE_6', 'Node_3'),
|
||||
('INSTANCE_7', 'Node_4')]
|
||||
edges = [(src.uuid, dst.uuid) for src, dst in model.edges()]
|
||||
self.assertEqual(sorted(expected_edges), sorted(edges))
|
||||
self.assertEqual(sorted(expected_edges), sorted(model.edges()))
|
||||
|
||||
419
watcher/tests/notifications/test_action_plan_notification.py
Normal file
419
watcher/tests/notifications/test_action_plan_notification.py
Normal file
@@ -0,0 +1,419 @@
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import freezegun
|
||||
import mock
|
||||
import oslo_messaging as om
|
||||
|
||||
from watcher.common import exception
|
||||
from watcher.common import rpc
|
||||
from watcher import notifications
|
||||
from watcher import objects
|
||||
from watcher.tests.db import base
|
||||
from watcher.tests.objects import utils
|
||||
|
||||
|
||||
@freezegun.freeze_time('2016-10-18T09:52:05.219414')
|
||||
class TestActionPlanNotification(base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestActionPlanNotification, self).setUp()
|
||||
p_get_notifier = mock.patch.object(rpc, 'get_notifier')
|
||||
m_get_notifier = p_get_notifier.start()
|
||||
self.addCleanup(p_get_notifier.stop)
|
||||
self.m_notifier = mock.Mock(spec=om.Notifier)
|
||||
|
||||
def fake_get_notifier(publisher_id):
|
||||
self.m_notifier.publisher_id = publisher_id
|
||||
return self.m_notifier
|
||||
|
||||
m_get_notifier.side_effect = fake_get_notifier
|
||||
self.goal = utils.create_test_goal(mock.Mock())
|
||||
self.audit = utils.create_test_audit(mock.Mock(), interval=None)
|
||||
self.strategy = utils.create_test_strategy(mock.Mock())
|
||||
|
||||
def test_send_invalid_action_plan(self):
|
||||
action_plan = utils.get_test_action_plan(
|
||||
mock.Mock(), state='DOESNOTMATTER', audit_id=1)
|
||||
|
||||
self.assertRaises(
|
||||
exception.InvalidActionPlan,
|
||||
notifications.action_plan.send_update,
|
||||
mock.MagicMock(), action_plan, host='node0')
|
||||
|
||||
def test_send_action_plan_update(self):
|
||||
action_plan = utils.create_test_action_plan(
|
||||
mock.Mock(), state=objects.action_plan.State.ONGOING,
|
||||
audit_id=self.audit.id, strategy_id=self.strategy.id,
|
||||
audit=self.audit, strategy=self.strategy)
|
||||
notifications.action_plan.send_update(
|
||||
mock.MagicMock(), action_plan, host='node0',
|
||||
old_state=objects.action_plan.State.PENDING)
|
||||
|
||||
# The 1st notification is because we created the object.
|
||||
# The 2nd notification is because we created the action plan object.
|
||||
self.assertEqual(3, self.m_notifier.info.call_count)
|
||||
notification = self.m_notifier.info.call_args[1]
|
||||
payload = notification['payload']
|
||||
|
||||
self.assertEqual("infra-optim:node0", self.m_notifier.publisher_id)
|
||||
self.assertDictEqual(
|
||||
{
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"global_efficacy": {},
|
||||
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"strategy": {
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"updated_at": None,
|
||||
"uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"name": "TEST",
|
||||
"parameters_spec": {},
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"display_name": "test strategy",
|
||||
"deleted_at": None
|
||||
},
|
||||
"watcher_object.name": "StrategyPayload"
|
||||
},
|
||||
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
|
||||
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"audit": {
|
||||
"watcher_object.data": {
|
||||
"interval": None,
|
||||
"parameters": {},
|
||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"strategy_uuid": None,
|
||||
"goal_uuid": (
|
||||
"f7ad87ae-4298-91cf-93a0-f35a852e3652"),
|
||||
"deleted_at": None,
|
||||
"scope": [],
|
||||
"state": "PENDING",
|
||||
"updated_at": None,
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"audit_type": "ONESHOT"
|
||||
},
|
||||
"watcher_object.name": "TerseAuditPayload",
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0"
|
||||
},
|
||||
"deleted_at": None,
|
||||
"state": "ONGOING",
|
||||
"updated_at": None,
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"state_update": {
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"old_state": "PENDING",
|
||||
"state": "ONGOING"
|
||||
},
|
||||
"watcher_object.name": "ActionPlanStateUpdatePayload"
|
||||
},
|
||||
},
|
||||
"watcher_object.name": "ActionPlanUpdatePayload"
|
||||
},
|
||||
payload
|
||||
)
|
||||
|
||||
def test_send_action_plan_create(self):
|
||||
action_plan = utils.get_test_action_plan(
|
||||
mock.Mock(), state=objects.action_plan.State.PENDING,
|
||||
audit_id=self.audit.id, strategy_id=self.strategy.id,
|
||||
audit=self.audit.as_dict(), strategy=self.strategy.as_dict())
|
||||
notifications.action_plan.send_create(
|
||||
mock.MagicMock(), action_plan, host='node0')
|
||||
|
||||
self.assertEqual(2, self.m_notifier.info.call_count)
|
||||
notification = self.m_notifier.info.call_args[1]
|
||||
payload = notification['payload']
|
||||
|
||||
self.assertEqual("infra-optim:node0", self.m_notifier.publisher_id)
|
||||
self.assertDictEqual(
|
||||
{
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"global_efficacy": {},
|
||||
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"strategy": {
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"updated_at": None,
|
||||
"uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"name": "TEST",
|
||||
"parameters_spec": {},
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"display_name": "test strategy",
|
||||
"deleted_at": None
|
||||
},
|
||||
"watcher_object.name": "StrategyPayload"
|
||||
},
|
||||
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
|
||||
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"audit": {
|
||||
"watcher_object.data": {
|
||||
"interval": None,
|
||||
"parameters": {},
|
||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"strategy_uuid": None,
|
||||
"goal_uuid": (
|
||||
"f7ad87ae-4298-91cf-93a0-f35a852e3652"),
|
||||
"deleted_at": None,
|
||||
"scope": [],
|
||||
"state": "PENDING",
|
||||
"updated_at": None,
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"audit_type": "ONESHOT"
|
||||
},
|
||||
"watcher_object.name": "TerseAuditPayload",
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0"
|
||||
},
|
||||
"deleted_at": None,
|
||||
"state": "PENDING",
|
||||
"updated_at": None,
|
||||
"created_at": None,
|
||||
},
|
||||
"watcher_object.name": "ActionPlanCreatePayload"
|
||||
},
|
||||
payload
|
||||
)
|
||||
|
||||
def test_send_action_plan_delete(self):
|
||||
action_plan = utils.create_test_action_plan(
|
||||
mock.Mock(), state=objects.action_plan.State.DELETED,
|
||||
audit_id=self.audit.id, strategy_id=self.strategy.id)
|
||||
notifications.action_plan.send_delete(
|
||||
mock.MagicMock(), action_plan, host='node0')
|
||||
|
||||
# The 1st notification is because we created the audit object.
|
||||
# The 2nd notification is because we created the action plan object.
|
||||
self.assertEqual(3, self.m_notifier.info.call_count)
|
||||
notification = self.m_notifier.info.call_args[1]
|
||||
payload = notification['payload']
|
||||
|
||||
self.assertEqual("infra-optim:node0", self.m_notifier.publisher_id)
|
||||
self.assertDictEqual(
|
||||
{
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"global_efficacy": {},
|
||||
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"strategy": {
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"updated_at": None,
|
||||
"uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"name": "TEST",
|
||||
"parameters_spec": {},
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"display_name": "test strategy",
|
||||
"deleted_at": None
|
||||
},
|
||||
"watcher_object.name": "StrategyPayload"
|
||||
},
|
||||
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
|
||||
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"audit": {
|
||||
"watcher_object.data": {
|
||||
"interval": None,
|
||||
"parameters": {},
|
||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"strategy_uuid": None,
|
||||
"goal_uuid": (
|
||||
"f7ad87ae-4298-91cf-93a0-f35a852e3652"),
|
||||
"deleted_at": None,
|
||||
"scope": [],
|
||||
"state": "PENDING",
|
||||
"updated_at": None,
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"audit_type": "ONESHOT"
|
||||
},
|
||||
"watcher_object.name": "TerseAuditPayload",
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0"
|
||||
},
|
||||
"deleted_at": None,
|
||||
"state": "DELETED",
|
||||
"updated_at": None,
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
},
|
||||
"watcher_object.name": "ActionPlanDeletePayload"
|
||||
},
|
||||
payload
|
||||
)
|
||||
|
||||
def test_send_action_plan_action(self):
|
||||
action_plan = utils.create_test_action_plan(
|
||||
mock.Mock(), state=objects.action_plan.State.ONGOING,
|
||||
audit_id=self.audit.id, strategy_id=self.strategy.id,
|
||||
audit=self.audit, strategy=self.strategy)
|
||||
notifications.action_plan.send_action_notification(
|
||||
mock.MagicMock(), action_plan, host='node0',
|
||||
action='execution', phase='start')
|
||||
|
||||
# The 1st notification is because we created the audit object.
|
||||
# The 2nd notification is because we created the action plan object.
|
||||
self.assertEqual(3, self.m_notifier.info.call_count)
|
||||
notification = self.m_notifier.info.call_args[1]
|
||||
|
||||
self.assertEqual("infra-optim:node0", self.m_notifier.publisher_id)
|
||||
self.assertDictEqual(
|
||||
{
|
||||
"event_type": "action_plan.execution.start",
|
||||
"payload": {
|
||||
"watcher_object.data": {
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"deleted_at": None,
|
||||
"fault": None,
|
||||
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"audit": {
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "TerseAuditPayload",
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"interval": None,
|
||||
"parameters": {},
|
||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"strategy_uuid": None,
|
||||
"goal_uuid": (
|
||||
"f7ad87ae-4298-91cf-93a0-f35a852e3652"),
|
||||
"deleted_at": None,
|
||||
"scope": [],
|
||||
"state": "PENDING",
|
||||
"updated_at": None,
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"audit_type": "ONESHOT"
|
||||
}
|
||||
},
|
||||
"global_efficacy": {},
|
||||
"state": "ONGOING",
|
||||
"strategy_uuid": (
|
||||
"cb3d0b58-4415-4d90-b75b-1e96878730e3"),
|
||||
"strategy": {
|
||||
"watcher_object.data": {
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"deleted_at": None,
|
||||
"display_name": "test strategy",
|
||||
"name": "TEST",
|
||||
"parameters_spec": {},
|
||||
"updated_at": None,
|
||||
"uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3"
|
||||
},
|
||||
"watcher_object.name": "StrategyPayload",
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0"
|
||||
},
|
||||
"updated_at": None,
|
||||
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061"
|
||||
},
|
||||
"watcher_object.name": "ActionPlanActionPayload",
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0"
|
||||
}
|
||||
},
|
||||
notification
|
||||
)
|
||||
|
||||
def test_send_action_plan_action_with_error(self):
|
||||
action_plan = utils.create_test_action_plan(
|
||||
mock.Mock(), state=objects.action_plan.State.ONGOING,
|
||||
audit_id=self.audit.id, strategy_id=self.strategy.id,
|
||||
audit=self.audit, strategy=self.strategy)
|
||||
|
||||
try:
|
||||
# This is to load the exception in sys.exc_info()
|
||||
raise exception.WatcherException("TEST")
|
||||
except exception.WatcherException:
|
||||
notifications.action_plan.send_action_notification(
|
||||
mock.MagicMock(), action_plan, host='node0',
|
||||
action='execution', priority='error', phase='error')
|
||||
|
||||
self.assertEqual(1, self.m_notifier.error.call_count)
|
||||
notification = self.m_notifier.error.call_args[1]
|
||||
self.assertEqual("infra-optim:node0", self.m_notifier.publisher_id)
|
||||
self.assertDictEqual(
|
||||
{
|
||||
"event_type": "action_plan.execution.error",
|
||||
"payload": {
|
||||
"watcher_object.data": {
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"deleted_at": None,
|
||||
"fault": {
|
||||
"watcher_object.data": {
|
||||
"exception": "WatcherException",
|
||||
"exception_message": "TEST",
|
||||
"function_name": (
|
||||
"test_send_action_plan_action_with_error"),
|
||||
"module_name": "watcher.tests.notifications."
|
||||
"test_action_plan_notification"
|
||||
},
|
||||
"watcher_object.name": "ExceptionPayload",
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0"
|
||||
},
|
||||
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"audit": {
|
||||
"watcher_object.data": {
|
||||
"interval": None,
|
||||
"parameters": {},
|
||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"strategy_uuid": None,
|
||||
"goal_uuid": (
|
||||
"f7ad87ae-4298-91cf-93a0-f35a852e3652"),
|
||||
"deleted_at": None,
|
||||
"scope": [],
|
||||
"state": "PENDING",
|
||||
"updated_at": None,
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"audit_type": "ONESHOT"
|
||||
},
|
||||
"watcher_object.name": "TerseAuditPayload",
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0"
|
||||
},
|
||||
"global_efficacy": {},
|
||||
"state": "ONGOING",
|
||||
"strategy_uuid": (
|
||||
"cb3d0b58-4415-4d90-b75b-1e96878730e3"),
|
||||
"strategy": {
|
||||
"watcher_object.data": {
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"deleted_at": None,
|
||||
"display_name": "test strategy",
|
||||
"name": "TEST",
|
||||
"parameters_spec": {},
|
||||
"updated_at": None,
|
||||
"uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3"
|
||||
},
|
||||
"watcher_object.name": "StrategyPayload",
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0"
|
||||
},
|
||||
"updated_at": None,
|
||||
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061"
|
||||
},
|
||||
"watcher_object.name": "ActionPlanActionPayload",
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0"
|
||||
}
|
||||
},
|
||||
notification
|
||||
)
|
||||
@@ -43,8 +43,8 @@ class TestAuditNotification(base.DbTestCase):
|
||||
self.strategy = utils.create_test_strategy(mock.Mock())
|
||||
|
||||
def test_send_invalid_audit(self):
|
||||
audit = utils.get_test_audit(mock.Mock(), state='DOESNOTMATTER',
|
||||
goal_id=1)
|
||||
audit = utils.get_test_audit(
|
||||
mock.Mock(), interval=None, state='DOESNOTMATTER', goal_id=1)
|
||||
|
||||
self.assertRaises(
|
||||
exception.InvalidAudit,
|
||||
@@ -53,7 +53,7 @@ class TestAuditNotification(base.DbTestCase):
|
||||
|
||||
def test_send_audit_update_with_strategy(self):
|
||||
audit = utils.create_test_audit(
|
||||
mock.Mock(), state=objects.audit.State.ONGOING,
|
||||
mock.Mock(), interval=None, state=objects.audit.State.ONGOING,
|
||||
goal_id=self.goal.id, strategy_id=self.strategy.id,
|
||||
goal=self.goal, strategy=self.strategy)
|
||||
notifications.audit.send_update(
|
||||
@@ -71,7 +71,8 @@ class TestAuditNotification(base.DbTestCase):
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"interval": 3600,
|
||||
"interval": None,
|
||||
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"strategy": {
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0",
|
||||
@@ -88,6 +89,7 @@ class TestAuditNotification(base.DbTestCase):
|
||||
},
|
||||
"parameters": {},
|
||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"goal_uuid": "f7ad87ae-4298-91cf-93a0-f35a852e3652",
|
||||
"goal": {
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0",
|
||||
@@ -125,7 +127,7 @@ class TestAuditNotification(base.DbTestCase):
|
||||
|
||||
def test_send_audit_update_without_strategy(self):
|
||||
audit = utils.get_test_audit(
|
||||
mock.Mock(), state=objects.audit.State.ONGOING,
|
||||
mock.Mock(), interval=None, state=objects.audit.State.ONGOING,
|
||||
goal_id=self.goal.id, goal=self.goal)
|
||||
notifications.audit.send_update(
|
||||
mock.MagicMock(), audit, host='node0',
|
||||
@@ -141,9 +143,10 @@ class TestAuditNotification(base.DbTestCase):
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"interval": 3600,
|
||||
"interval": None,
|
||||
"parameters": {},
|
||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"goal_uuid": "f7ad87ae-4298-91cf-93a0-f35a852e3652",
|
||||
"goal": {
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0",
|
||||
@@ -158,6 +161,7 @@ class TestAuditNotification(base.DbTestCase):
|
||||
},
|
||||
"watcher_object.name": "GoalPayload"
|
||||
},
|
||||
"strategy_uuid": None,
|
||||
"strategy": None,
|
||||
"deleted_at": None,
|
||||
"scope": [],
|
||||
@@ -182,7 +186,7 @@ class TestAuditNotification(base.DbTestCase):
|
||||
|
||||
def test_send_audit_create(self):
|
||||
audit = utils.get_test_audit(
|
||||
mock.Mock(), state=objects.audit.State.PENDING,
|
||||
mock.Mock(), interval=None, state=objects.audit.State.PENDING,
|
||||
goal_id=self.goal.id, strategy_id=self.strategy.id,
|
||||
goal=self.goal.as_dict(), strategy=self.strategy.as_dict())
|
||||
notifications.audit.send_create(
|
||||
@@ -198,7 +202,8 @@ class TestAuditNotification(base.DbTestCase):
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"interval": 3600,
|
||||
"interval": None,
|
||||
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"strategy": {
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0",
|
||||
@@ -215,6 +220,7 @@ class TestAuditNotification(base.DbTestCase):
|
||||
},
|
||||
"parameters": {},
|
||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"goal_uuid": "f7ad87ae-4298-91cf-93a0-f35a852e3652",
|
||||
"goal": {
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0",
|
||||
@@ -243,7 +249,7 @@ class TestAuditNotification(base.DbTestCase):
|
||||
|
||||
def test_send_audit_delete(self):
|
||||
audit = utils.create_test_audit(
|
||||
mock.Mock(), state=objects.audit.State.DELETED,
|
||||
mock.Mock(), interval=None, state=objects.audit.State.DELETED,
|
||||
goal_id=self.goal.id, strategy_id=self.strategy.id)
|
||||
notifications.audit.send_delete(
|
||||
mock.MagicMock(), audit, host='node0')
|
||||
@@ -259,7 +265,8 @@ class TestAuditNotification(base.DbTestCase):
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"interval": 3600,
|
||||
"interval": None,
|
||||
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"strategy": {
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0",
|
||||
@@ -276,6 +283,7 @@ class TestAuditNotification(base.DbTestCase):
|
||||
},
|
||||
"parameters": {},
|
||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"goal_uuid": "f7ad87ae-4298-91cf-93a0-f35a852e3652",
|
||||
"goal": {
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0",
|
||||
@@ -304,7 +312,7 @@ class TestAuditNotification(base.DbTestCase):
|
||||
|
||||
def test_send_audit_action(self):
|
||||
audit = utils.create_test_audit(
|
||||
mock.Mock(), state=objects.audit.State.ONGOING,
|
||||
mock.Mock(), interval=None, state=objects.audit.State.ONGOING,
|
||||
goal_id=self.goal.id, strategy_id=self.strategy.id,
|
||||
goal=self.goal, strategy=self.strategy)
|
||||
notifications.audit.send_action_notification(
|
||||
@@ -326,6 +334,7 @@ class TestAuditNotification(base.DbTestCase):
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"deleted_at": None,
|
||||
"fault": None,
|
||||
"goal_uuid": "f7ad87ae-4298-91cf-93a0-f35a852e3652",
|
||||
"goal": {
|
||||
"watcher_object.data": {
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
@@ -340,10 +349,12 @@ class TestAuditNotification(base.DbTestCase):
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0"
|
||||
},
|
||||
"interval": 3600,
|
||||
"interval": None,
|
||||
"parameters": {},
|
||||
"scope": [],
|
||||
"state": "ONGOING",
|
||||
"strategy_uuid": (
|
||||
"cb3d0b58-4415-4d90-b75b-1e96878730e3"),
|
||||
"strategy": {
|
||||
"watcher_object.data": {
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
@@ -371,7 +382,7 @@ class TestAuditNotification(base.DbTestCase):
|
||||
|
||||
def test_send_audit_action_with_error(self):
|
||||
audit = utils.create_test_audit(
|
||||
mock.Mock(), state=objects.audit.State.ONGOING,
|
||||
mock.Mock(), interval=None, state=objects.audit.State.ONGOING,
|
||||
goal_id=self.goal.id, strategy_id=self.strategy.id,
|
||||
goal=self.goal, strategy=self.strategy)
|
||||
|
||||
@@ -407,6 +418,7 @@ class TestAuditNotification(base.DbTestCase):
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0"
|
||||
},
|
||||
"goal_uuid": "f7ad87ae-4298-91cf-93a0-f35a852e3652",
|
||||
"goal": {
|
||||
"watcher_object.data": {
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
@@ -421,10 +433,12 @@ class TestAuditNotification(base.DbTestCase):
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.version": "1.0"
|
||||
},
|
||||
"interval": 3600,
|
||||
"interval": None,
|
||||
"parameters": {},
|
||||
"scope": [],
|
||||
"state": "ONGOING",
|
||||
"strategy_uuid": (
|
||||
"cb3d0b58-4415-4d90-b75b-1e96878730e3"),
|
||||
"strategy": {
|
||||
"watcher_object.data": {
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
|
||||
@@ -250,10 +250,11 @@ class TestNotificationBase(testbase.TestCase):
|
||||
|
||||
|
||||
expected_notification_fingerprints = {
|
||||
'EventType': '1.2-633c2d32fa849d2a6f8bda3b0db88332',
|
||||
'EventType': '1.3-4258a2c86eca79fd34a7dffe1278eab9',
|
||||
'ExceptionNotification': '1.0-9b69de0724fda8310d05e18418178866',
|
||||
'ExceptionPayload': '1.0-4516ae282a55fe2fd5c754967ee6248b',
|
||||
'NotificationPublisher': '1.0-bbbc1402fb0e443a3eb227cc52b61545',
|
||||
'TerseAuditPayload': '1.0-aaf31166b8698f08d12cae98c380b8e0',
|
||||
'AuditPayload': '1.0-30c85c834648c8ca11f54fc5e084d86b',
|
||||
'AuditStateUpdatePayload': '1.0-1a1b606bf14a2c468800c2b010801ce5',
|
||||
'AuditUpdateNotification': '1.0-9b69de0724fda8310d05e18418178866',
|
||||
@@ -266,6 +267,16 @@ expected_notification_fingerprints = {
|
||||
'AuditActionPayload': '1.0-09f5d005f94ba9e5f6b9200170332c52',
|
||||
'GoalPayload': '1.0-fa1fecb8b01dd047eef808ded4d50d1a',
|
||||
'StrategyPayload': '1.0-94f01c137b083ac236ae82573c1fcfc1',
|
||||
'ActionPlanActionPayload': '1.0-34871caf18e9b43a28899953c1c9733a',
|
||||
'ActionPlanCreateNotification': '1.0-9b69de0724fda8310d05e18418178866',
|
||||
'ActionPlanCreatePayload': '1.0-ffc3087acd73351b14f3dcc30e105027',
|
||||
'ActionPlanDeleteNotification': '1.0-9b69de0724fda8310d05e18418178866',
|
||||
'ActionPlanDeletePayload': '1.0-ffc3087acd73351b14f3dcc30e105027',
|
||||
'ActionPlanPayload': '1.0-ffc3087acd73351b14f3dcc30e105027',
|
||||
'ActionPlanStateUpdatePayload': '1.0-1a1b606bf14a2c468800c2b010801ce5',
|
||||
'ActionPlanUpdateNotification': '1.0-9b69de0724fda8310d05e18418178866',
|
||||
'ActionPlanUpdatePayload': '1.0-7912a45fe53775c721f42aa87f06a023',
|
||||
'ActionPlanActionNotification': '1.0-9b69de0724fda8310d05e18418178866',
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import mock
|
||||
|
||||
from watcher.common import exception
|
||||
from watcher.db.sqlalchemy import api as db_api
|
||||
from watcher import notifications
|
||||
from watcher import objects
|
||||
from watcher.tests.db import base
|
||||
from watcher.tests.db import utils
|
||||
@@ -34,16 +35,19 @@ class TestActionPlanObject(base.DbTestCase):
|
||||
('non_eager', dict(
|
||||
eager=False,
|
||||
fake_action_plan=utils.get_test_action_plan(
|
||||
created_at=datetime.datetime.utcnow(),
|
||||
audit_id=audit_id,
|
||||
strategy_id=strategy_id))),
|
||||
('eager_with_non_eager_load', dict(
|
||||
eager=True,
|
||||
fake_action_plan=utils.get_test_action_plan(
|
||||
created_at=datetime.datetime.utcnow(),
|
||||
audit_id=audit_id,
|
||||
strategy_id=strategy_id))),
|
||||
('eager_with_eager_load', dict(
|
||||
eager=True,
|
||||
fake_action_plan=utils.get_test_action_plan(
|
||||
created_at=datetime.datetime.utcnow(),
|
||||
strategy_id=strategy_id,
|
||||
strategy=utils.get_test_strategy(id=strategy_id),
|
||||
audit_id=audit_id,
|
||||
@@ -52,6 +56,13 @@ class TestActionPlanObject(base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestActionPlanObject, self).setUp()
|
||||
|
||||
p_action_plan_notifications = mock.patch.object(
|
||||
notifications, 'action_plan', autospec=True)
|
||||
self.m_action_plan_notifications = p_action_plan_notifications.start()
|
||||
self.addCleanup(p_action_plan_notifications.stop)
|
||||
self.m_send_update = self.m_action_plan_notifications.send_update
|
||||
|
||||
self.fake_audit = utils.create_test_audit(id=self.audit_id)
|
||||
self.fake_strategy = utils.create_test_strategy(
|
||||
id=self.strategy_id, name="DUMMY")
|
||||
@@ -80,6 +91,7 @@ class TestActionPlanObject(base.DbTestCase):
|
||||
self.context, action_plan_id, eager=self.eager)
|
||||
self.assertEqual(self.context, action_plan._context)
|
||||
self.eager_load_action_plan_assert(action_plan)
|
||||
self.assertEqual(0, self.m_send_update.call_count)
|
||||
|
||||
@mock.patch.object(db_api.Connection, 'get_action_plan_by_uuid')
|
||||
def test_get_by_uuid(self, mock_get_action_plan):
|
||||
@@ -91,6 +103,7 @@ class TestActionPlanObject(base.DbTestCase):
|
||||
self.context, uuid, eager=self.eager)
|
||||
self.assertEqual(self.context, action_plan._context)
|
||||
self.eager_load_action_plan_assert(action_plan)
|
||||
self.assertEqual(0, self.m_send_update.call_count)
|
||||
|
||||
def test_get_bad_id_and_uuid(self):
|
||||
self.assertRaises(exception.InvalidIdentity,
|
||||
@@ -107,14 +120,26 @@ class TestActionPlanObject(base.DbTestCase):
|
||||
self.assertEqual(self.context, action_plans[0]._context)
|
||||
for action_plan in action_plans:
|
||||
self.eager_load_action_plan_assert(action_plan)
|
||||
self.assertEqual(0, self.m_send_update.call_count)
|
||||
|
||||
@mock.patch.object(db_api.Connection, 'update_action_plan')
|
||||
@mock.patch.object(db_api.Connection, 'get_action_plan_by_uuid')
|
||||
def test_save(self, mock_get_action_plan, mock_update_action_plan):
|
||||
mock_get_action_plan.return_value = self.fake_action_plan
|
||||
fake_saved_action_plan = self.fake_action_plan.copy()
|
||||
fake_saved_action_plan['deleted_at'] = datetime.datetime.utcnow()
|
||||
fake_saved_action_plan['state'] = objects.action_plan.State.SUCCEEDED
|
||||
fake_saved_action_plan['updated_at'] = datetime.datetime.utcnow()
|
||||
|
||||
mock_update_action_plan.return_value = fake_saved_action_plan
|
||||
|
||||
expected_action_plan = fake_saved_action_plan.copy()
|
||||
expected_action_plan[
|
||||
'created_at'] = expected_action_plan['created_at'].replace(
|
||||
tzinfo=iso8601.iso8601.Utc())
|
||||
expected_action_plan[
|
||||
'updated_at'] = expected_action_plan['updated_at'].replace(
|
||||
tzinfo=iso8601.iso8601.Utc())
|
||||
|
||||
uuid = self.fake_action_plan['uuid']
|
||||
action_plan = objects.ActionPlan.get_by_uuid(
|
||||
self.context, uuid, eager=self.eager)
|
||||
@@ -127,6 +152,14 @@ class TestActionPlanObject(base.DbTestCase):
|
||||
uuid, {'state': objects.action_plan.State.SUCCEEDED})
|
||||
self.assertEqual(self.context, action_plan._context)
|
||||
self.eager_load_action_plan_assert(action_plan)
|
||||
self.m_send_update.assert_called_once_with(
|
||||
self.context, action_plan,
|
||||
old_state=self.fake_action_plan['state'])
|
||||
self.assertEqual(
|
||||
{k: v for k, v in expected_action_plan.items()
|
||||
if k not in action_plan.object_fields},
|
||||
{k: v for k, v in action_plan.as_dict().items()
|
||||
if k not in action_plan.object_fields})
|
||||
|
||||
@mock.patch.object(db_api.Connection, 'get_action_plan_by_uuid')
|
||||
def test_refresh(self, mock_get_action_plan):
|
||||
@@ -150,6 +183,13 @@ class TestCreateDeleteActionPlanObject(base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestCreateDeleteActionPlanObject, self).setUp()
|
||||
|
||||
p_action_plan_notifications = mock.patch.object(
|
||||
notifications, 'action_plan', autospec=True)
|
||||
self.m_action_plan_notifications = p_action_plan_notifications.start()
|
||||
self.addCleanup(p_action_plan_notifications.stop)
|
||||
self.m_send_update = self.m_action_plan_notifications.send_update
|
||||
|
||||
self.fake_strategy = utils.create_test_strategy(name="DUMMY")
|
||||
self.fake_audit = utils.create_test_audit()
|
||||
self.fake_action_plan = utils.get_test_action_plan(
|
||||
@@ -202,7 +242,8 @@ class TestCreateDeleteActionPlanObject(base.DbTestCase):
|
||||
del expected_action_plan['strategy']
|
||||
|
||||
m_get_efficacy_indicator_list.return_value = [efficacy_indicator]
|
||||
action_plan = objects.ActionPlan.get_by_uuid(self.context, uuid)
|
||||
action_plan = objects.ActionPlan.get_by_uuid(
|
||||
self.context, uuid, eager=False)
|
||||
action_plan.soft_delete()
|
||||
|
||||
m_get_action_plan.assert_called_once_with(
|
||||
|
||||
@@ -282,6 +282,7 @@ class TestAuditObjectSendNotifications(base.DbTestCase):
|
||||
def test_send_create_notification(self, m_create_audit):
|
||||
audit = objutils.get_test_audit(
|
||||
self.context,
|
||||
id=1,
|
||||
goal_id=self.fake_goal.id,
|
||||
strategy_id=self.fake_strategy.id,
|
||||
goal=self.fake_goal.as_dict(),
|
||||
|
||||
@@ -30,21 +30,27 @@ def _load_related_objects(context, cls, db_data):
|
||||
return obj_data
|
||||
|
||||
|
||||
def _load_test_obj(context, cls, obj_data, **kw):
|
||||
# Let DB generate ID if it isn't specified explicitly
|
||||
if 'id' not in kw:
|
||||
del obj_data['id']
|
||||
obj = cls(context)
|
||||
for key in obj_data:
|
||||
setattr(obj, key, obj_data[key])
|
||||
return obj
|
||||
|
||||
|
||||
def get_test_audit_template(context, **kw):
|
||||
"""Return a AuditTemplate object with appropriate attributes.
|
||||
|
||||
NOTE: The object leaves the attributes marked as changed, such
|
||||
that a create() could be used to commit it to the DB.
|
||||
"""
|
||||
db_audit_template = db_utils.get_test_audit_template(**kw)
|
||||
# Let DB generate ID if it isn't specified explicitly
|
||||
if 'id' not in kw:
|
||||
del db_audit_template['id']
|
||||
audit_template = objects.AuditTemplate(context)
|
||||
for key in db_audit_template:
|
||||
setattr(audit_template, key, db_audit_template[key])
|
||||
obj_cls = objects.AuditTemplate
|
||||
db_data = db_utils.get_test_audit_template(**kw)
|
||||
obj_data = _load_related_objects(context, obj_cls, db_data)
|
||||
|
||||
return audit_template
|
||||
return _load_test_obj(context, obj_cls, obj_data, **kw)
|
||||
|
||||
|
||||
def create_test_audit_template(context, **kw):
|
||||
@@ -64,16 +70,11 @@ def get_test_audit(context, **kw):
|
||||
NOTE: The object leaves the attributes marked as changed, such
|
||||
that a create() could be used to commit it to the DB.
|
||||
"""
|
||||
db_audit = db_utils.get_test_audit(**kw)
|
||||
obj_data = _load_related_objects(context, objects.Audit, db_audit)
|
||||
obj_cls = objects.Audit
|
||||
db_data = db_utils.get_test_audit(**kw)
|
||||
obj_data = _load_related_objects(context, obj_cls, db_data)
|
||||
|
||||
# Let DB generate ID if it isn't specified explicitly
|
||||
if 'id' not in kw:
|
||||
del db_audit['id']
|
||||
audit = objects.Audit(context)
|
||||
for key in obj_data:
|
||||
setattr(audit, key, obj_data[key])
|
||||
return audit
|
||||
return _load_test_obj(context, obj_cls, obj_data, **kw)
|
||||
|
||||
|
||||
def create_test_audit(context, **kw):
|
||||
@@ -93,14 +94,11 @@ def get_test_action_plan(context, **kw):
|
||||
NOTE: The object leaves the attributes marked as changed, such
|
||||
that a create() could be used to commit it to the DB.
|
||||
"""
|
||||
db_action_plan = db_utils.get_test_action_plan(**kw)
|
||||
# Let DB generate ID if it isn't specified explicitly
|
||||
if 'id' not in kw:
|
||||
del db_action_plan['id']
|
||||
action_plan = objects.ActionPlan(context)
|
||||
for key in db_action_plan:
|
||||
setattr(action_plan, key, db_action_plan[key])
|
||||
return action_plan
|
||||
obj_cls = objects.ActionPlan
|
||||
db_data = db_utils.get_test_action_plan(**kw)
|
||||
obj_data = _load_related_objects(context, obj_cls, db_data)
|
||||
|
||||
return _load_test_obj(context, obj_cls, obj_data, **kw)
|
||||
|
||||
|
||||
def create_test_action_plan(context, **kw):
|
||||
@@ -120,14 +118,11 @@ def get_test_action(context, **kw):
|
||||
NOTE: The object leaves the attributes marked as changed, such
|
||||
that a create() could be used to commit it to the DB.
|
||||
"""
|
||||
db_action = db_utils.get_test_action(**kw)
|
||||
# Let DB generate ID if it isn't specified explicitly
|
||||
if 'id' not in kw:
|
||||
del db_action['id']
|
||||
action = objects.Action(context)
|
||||
for key in db_action:
|
||||
setattr(action, key, db_action[key])
|
||||
return action
|
||||
obj_cls = objects.Action
|
||||
db_data = db_utils.get_test_action(**kw)
|
||||
obj_data = _load_related_objects(context, obj_cls, db_data)
|
||||
|
||||
return _load_test_obj(context, obj_cls, obj_data, **kw)
|
||||
|
||||
|
||||
def create_test_action(context, **kw):
|
||||
@@ -147,14 +142,11 @@ def get_test_goal(context, **kw):
|
||||
NOTE: The object leaves the attributes marked as changed, such
|
||||
that a create() could be used to commit it to the DB.
|
||||
"""
|
||||
db_goal = db_utils.get_test_goal(**kw)
|
||||
# Let DB generate ID if it isn't specified explicitly
|
||||
if 'id' not in kw:
|
||||
del db_goal['id']
|
||||
goal = objects.Goal(context)
|
||||
for key in db_goal:
|
||||
setattr(goal, key, db_goal[key])
|
||||
return goal
|
||||
obj_cls = objects.Goal
|
||||
db_data = db_utils.get_test_goal(**kw)
|
||||
obj_data = _load_related_objects(context, obj_cls, db_data)
|
||||
|
||||
return _load_test_obj(context, obj_cls, obj_data, **kw)
|
||||
|
||||
|
||||
def create_test_goal(context, **kw):
|
||||
@@ -174,11 +166,11 @@ def get_test_scoring_engine(context, **kw):
|
||||
NOTE: The object leaves the attributes marked as changed, such
|
||||
that a create() could be used to commit it to the DB.
|
||||
"""
|
||||
db_scoring_engine = db_utils.get_test_scoring_engine(**kw)
|
||||
scoring_engine = objects.ScoringEngine(context)
|
||||
for key in db_scoring_engine:
|
||||
setattr(scoring_engine, key, db_scoring_engine[key])
|
||||
return scoring_engine
|
||||
obj_cls = objects.ScoringEngine
|
||||
db_data = db_utils.get_test_scoring_engine(**kw)
|
||||
obj_data = _load_related_objects(context, obj_cls, db_data)
|
||||
|
||||
return _load_test_obj(context, obj_cls, obj_data, **kw)
|
||||
|
||||
|
||||
def create_test_scoring_engine(context, **kw):
|
||||
@@ -198,13 +190,11 @@ def get_test_service(context, **kw):
|
||||
NOTE: The object leaves the attributes marked as changed, such
|
||||
that a create() could be used to commit it to the DB.
|
||||
"""
|
||||
db_service = db_utils.get_test_service(**kw)
|
||||
service = objects.Service(context)
|
||||
for key in db_service:
|
||||
if key == 'last_seen_up':
|
||||
db_service[key] = None
|
||||
setattr(service, key, db_service[key])
|
||||
return service
|
||||
obj_cls = objects.Service
|
||||
db_data = db_utils.get_test_service(**kw)
|
||||
obj_data = _load_related_objects(context, obj_cls, db_data)
|
||||
|
||||
return _load_test_obj(context, obj_cls, obj_data, **kw)
|
||||
|
||||
|
||||
def create_test_service(context, **kw):
|
||||
@@ -224,22 +214,11 @@ def get_test_strategy(context, **kw):
|
||||
NOTE: The object leaves the attributes marked as changed, such
|
||||
that a create() could be used to commit it to the DB.
|
||||
"""
|
||||
db_strategy = db_utils.get_test_strategy(**kw)
|
||||
# Let DB generate ID if it isn't specified explicitly
|
||||
if 'id' not in kw:
|
||||
del db_strategy['id']
|
||||
strategy = objects.Strategy(context)
|
||||
for key in db_strategy:
|
||||
setattr(strategy, key, db_strategy[key])
|
||||
obj_cls = objects.Strategy
|
||||
db_data = db_utils.get_test_strategy(**kw)
|
||||
obj_data = _load_related_objects(context, obj_cls, db_data)
|
||||
|
||||
# ObjectField checks for the object type, so if we want to simulate a
|
||||
# non-eager object loading, the field should not be referenced at all.
|
||||
# Contrarily, eager loading need the data to be casted to the object type
|
||||
# that was specified by the ObjectField.
|
||||
if kw.get('goal'):
|
||||
strategy.goal = objects.Goal(context, **kw.get('goal'))
|
||||
|
||||
return strategy
|
||||
return _load_test_obj(context, obj_cls, obj_data, **kw)
|
||||
|
||||
|
||||
def create_test_strategy(context, **kw):
|
||||
@@ -259,14 +238,11 @@ def get_test_efficacy_indicator(context, **kw):
|
||||
NOTE: The object leaves the attributes marked as changed, such
|
||||
that a create() could be used to commit it to the DB.
|
||||
"""
|
||||
db_efficacy_indicator = db_utils.get_test_efficacy_indicator(**kw)
|
||||
# Let DB generate ID if it isn't specified explicitly
|
||||
if 'id' not in kw:
|
||||
del db_efficacy_indicator['id']
|
||||
efficacy_indicator = objects.EfficacyIndicator(context)
|
||||
for key in db_efficacy_indicator:
|
||||
setattr(efficacy_indicator, key, db_efficacy_indicator[key])
|
||||
return efficacy_indicator
|
||||
obj_cls = objects.EfficacyIndicator
|
||||
db_data = db_utils.get_test_efficacy_indicator(**kw)
|
||||
obj_data = _load_related_objects(context, obj_cls, db_data)
|
||||
|
||||
return _load_test_obj(context, obj_cls, obj_data, **kw)
|
||||
|
||||
|
||||
def create_test_efficacy_indicator(context, **kw):
|
||||
|
||||
@@ -213,6 +213,18 @@ class InfraOptimClientJSON(base.BaseInfraOptimClient):
|
||||
|
||||
return self._patch_request('/action_plans', action_plan_uuid, patch)
|
||||
|
||||
@base.handle_errors
|
||||
def start_action_plan(self, action_plan_uuid):
|
||||
"""Start the specified action plan
|
||||
|
||||
:param action_plan_uuid: The unique identifier of the action_plan
|
||||
:return: Tuple with the server response and the updated action_plan
|
||||
"""
|
||||
|
||||
return self._patch_request(
|
||||
'/action_plans', action_plan_uuid,
|
||||
[{'path': '/state', 'op': 'replace', 'value': 'PENDING'}])
|
||||
|
||||
# ### GOALS ### #
|
||||
|
||||
@base.handle_errors
|
||||
|
||||
@@ -214,7 +214,7 @@ class BaseInfraOptimTest(test.BaseTestCase):
|
||||
audit_uuid = audit['uuid']
|
||||
|
||||
assert test.call_until_true(
|
||||
func=functools.partial(cls.has_audit_succeeded, audit_uuid),
|
||||
func=functools.partial(cls.has_audit_finished, audit_uuid),
|
||||
duration=30,
|
||||
sleep_for=.5
|
||||
)
|
||||
|
||||
@@ -35,7 +35,7 @@ class TestShowListAction(base.BaseInfraOptimTest):
|
||||
_, cls.audit = cls.create_audit(cls.audit_template['uuid'])
|
||||
|
||||
assert test.call_until_true(
|
||||
func=functools.partial(cls.has_audit_succeeded, cls.audit['uuid']),
|
||||
func=functools.partial(cls.has_audit_finished, cls.audit['uuid']),
|
||||
duration=30,
|
||||
sleep_for=.5
|
||||
)
|
||||
@@ -45,19 +45,23 @@ class TestShowListAction(base.BaseInfraOptimTest):
|
||||
|
||||
@test.attr(type='smoke')
|
||||
def test_show_one_action(self):
|
||||
_, action_uuid = self.client.list_actions(
|
||||
action_plan_uuid=self.action_plan['uuid'])['actions'][0]['uuid']
|
||||
_, action = self.client.show_action(action_uuid)
|
||||
_, body = self.client.list_actions(
|
||||
action_plan_uuid=self.action_plan["uuid"])
|
||||
actions = body['actions']
|
||||
|
||||
self.assertEqual(action_uuid, action['uuid'])
|
||||
self.assertEqual("nop", action['action_type'])
|
||||
_, action = self.client.show_action(actions[0]["uuid"])
|
||||
|
||||
self.assertEqual(self.action_plan["uuid"], action['action_plan_uuid'])
|
||||
self.assertEqual("PENDING", action['state'])
|
||||
|
||||
@test.attr(type='smoke')
|
||||
def test_show_action_with_links(self):
|
||||
_, action_uuid = self.client.list_actions(
|
||||
action_plan_uuid=self.action_plan['uuid'])['actions'][0]['uuid']
|
||||
_, action = self.client.show_action(action_uuid)
|
||||
_, body = self.client.list_actions(
|
||||
action_plan_uuid=self.action_plan["uuid"])
|
||||
actions = body['actions']
|
||||
|
||||
_, action = self.client.show_action(actions[0]["uuid"])
|
||||
|
||||
self.assertIn('links', action.keys())
|
||||
self.assertEqual(2, len(action['links']))
|
||||
self.assertIn(action['uuid'], action['links'][0]['href'])
|
||||
|
||||
@@ -29,12 +29,12 @@ class TestCreateDeleteExecuteActionPlan(base.BaseInfraOptimTest):
|
||||
|
||||
@test.attr(type='smoke')
|
||||
def test_create_action_plan(self):
|
||||
_, goal = self.client.show_goal("DUMMY")
|
||||
_, goal = self.client.show_goal("dummy")
|
||||
_, audit_template = self.create_audit_template(goal['uuid'])
|
||||
_, audit = self.create_audit(audit_template['uuid'])
|
||||
|
||||
self.assertTrue(test.call_until_true(
|
||||
func=functools.partial(self.has_audit_succeeded, audit['uuid']),
|
||||
func=functools.partial(self.has_audit_finished, audit['uuid']),
|
||||
duration=30,
|
||||
sleep_for=.5
|
||||
))
|
||||
@@ -49,12 +49,12 @@ class TestCreateDeleteExecuteActionPlan(base.BaseInfraOptimTest):
|
||||
|
||||
@test.attr(type='smoke')
|
||||
def test_delete_action_plan(self):
|
||||
_, goal = self.client.show_goal("DUMMY")
|
||||
_, goal = self.client.show_goal("dummy")
|
||||
_, audit_template = self.create_audit_template(goal['uuid'])
|
||||
_, audit = self.create_audit(audit_template['uuid'])
|
||||
|
||||
self.assertTrue(test.call_until_true(
|
||||
func=functools.partial(self.has_audit_succeeded, audit['uuid']),
|
||||
func=functools.partial(self.has_audit_finished, audit['uuid']),
|
||||
duration=30,
|
||||
sleep_for=.5
|
||||
))
|
||||
@@ -71,12 +71,12 @@ class TestCreateDeleteExecuteActionPlan(base.BaseInfraOptimTest):
|
||||
|
||||
@test.attr(type='smoke')
|
||||
def test_execute_dummy_action_plan(self):
|
||||
_, goal = self.client.show_goal("DUMMY")
|
||||
_, goal = self.client.show_goal("dummy")
|
||||
_, audit_template = self.create_audit_template(goal['uuid'])
|
||||
_, audit = self.create_audit(audit_template['uuid'])
|
||||
|
||||
self.assertTrue(test.call_until_true(
|
||||
func=functools.partial(self.has_audit_succeeded, audit['uuid']),
|
||||
func=functools.partial(self.has_audit_finished, audit['uuid']),
|
||||
duration=30,
|
||||
sleep_for=.5
|
||||
))
|
||||
@@ -86,11 +86,13 @@ class TestCreateDeleteExecuteActionPlan(base.BaseInfraOptimTest):
|
||||
|
||||
_, action_plan = self.client.show_action_plan(action_plan['uuid'])
|
||||
|
||||
if action_plan['state'] in ['SUPERSEDED', 'SUCCEEDED']:
|
||||
# This means the action plan is superseded so we cannot trigger it,
|
||||
# or it is empty.
|
||||
return
|
||||
|
||||
# Execute the action by changing its state to PENDING
|
||||
_, updated_ap = self.client.update_action_plan(
|
||||
action_plan['uuid'],
|
||||
patch=[{'path': '/state', 'op': 'replace', 'value': 'PENDING'}]
|
||||
)
|
||||
_, updated_ap = self.client.start_action_plan(action_plan['uuid'])
|
||||
|
||||
self.assertTrue(test.call_until_true(
|
||||
func=functools.partial(
|
||||
@@ -110,12 +112,12 @@ class TestShowListActionPlan(base.BaseInfraOptimTest):
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(TestShowListActionPlan, cls).resource_setup()
|
||||
_, cls.goal = cls.client.show_goal("DUMMY")
|
||||
_, cls.goal = cls.client.show_goal("dummy")
|
||||
_, cls.audit_template = cls.create_audit_template(cls.goal['uuid'])
|
||||
_, cls.audit = cls.create_audit(cls.audit_template['uuid'])
|
||||
|
||||
assert test.call_until_true(
|
||||
func=functools.partial(cls.has_audit_succeeded, cls.audit['uuid']),
|
||||
func=functools.partial(cls.has_audit_finished, cls.audit['uuid']),
|
||||
duration=30,
|
||||
sleep_for=.5
|
||||
)
|
||||
|
||||
@@ -38,7 +38,7 @@ class TestCreateUpdateDeleteAudit(base.BaseInfraOptimTest):
|
||||
|
||||
@test.attr(type='smoke')
|
||||
def test_create_audit_oneshot(self):
|
||||
_, goal = self.client.show_goal("DUMMY")
|
||||
_, goal = self.client.show_goal("dummy")
|
||||
_, audit_template = self.create_audit_template(goal['uuid'])
|
||||
|
||||
audit_params = dict(
|
||||
@@ -56,7 +56,7 @@ class TestCreateUpdateDeleteAudit(base.BaseInfraOptimTest):
|
||||
|
||||
@test.attr(type='smoke')
|
||||
def test_create_audit_continuous(self):
|
||||
_, goal = self.client.show_goal("DUMMY")
|
||||
_, goal = self.client.show_goal("dummy")
|
||||
_, audit_template = self.create_audit_template(goal['uuid'])
|
||||
|
||||
audit_params = dict(
|
||||
@@ -85,7 +85,7 @@ class TestCreateUpdateDeleteAudit(base.BaseInfraOptimTest):
|
||||
|
||||
@test.attr(type='smoke')
|
||||
def test_create_audit_with_invalid_state(self):
|
||||
_, goal = self.client.show_goal("DUMMY")
|
||||
_, goal = self.client.show_goal("dummy")
|
||||
_, audit_template = self.create_audit_template(goal['uuid'])
|
||||
|
||||
audit_params = dict(
|
||||
@@ -98,7 +98,7 @@ class TestCreateUpdateDeleteAudit(base.BaseInfraOptimTest):
|
||||
|
||||
@test.attr(type='smoke')
|
||||
def test_create_audit_with_no_state(self):
|
||||
_, goal = self.client.show_goal("DUMMY")
|
||||
_, goal = self.client.show_goal("dummy")
|
||||
_, audit_template = self.create_audit_template(goal['uuid'])
|
||||
|
||||
audit_params = dict(
|
||||
@@ -120,7 +120,7 @@ class TestCreateUpdateDeleteAudit(base.BaseInfraOptimTest):
|
||||
|
||||
@test.attr(type='smoke')
|
||||
def test_delete_audit(self):
|
||||
_, goal = self.client.show_goal("DUMMY")
|
||||
_, goal = self.client.show_goal("dummy")
|
||||
_, audit_template = self.create_audit_template(goal['uuid'])
|
||||
_, body = self.create_audit(audit_template['uuid'])
|
||||
audit_uuid = body['uuid']
|
||||
@@ -158,7 +158,7 @@ class TestShowListAudit(base.BaseInfraOptimTest):
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(TestShowListAudit, cls).resource_setup()
|
||||
_, cls.goal = cls.client.show_goal("DUMMY")
|
||||
_, cls.goal = cls.client.show_goal("dummy")
|
||||
_, cls.audit_template = cls.create_audit_template(cls.goal['uuid'])
|
||||
_, cls.audit = cls.create_audit(cls.audit_template['uuid'])
|
||||
|
||||
|
||||
@@ -36,6 +36,11 @@ CONF = config.CONF
|
||||
class BaseInfraOptimScenarioTest(manager.ScenarioTest):
|
||||
"""Base class for Infrastructure Optimization API tests."""
|
||||
|
||||
# States where the object is waiting for some event to perform a transition
|
||||
IDLE_STATES = ('RECOMMENDED', 'FAILED', 'SUCCEEDED', 'CANCELLED')
|
||||
# States where the object can only be DELETED (end of its life-cycle)
|
||||
FINISHED_STATES = ('FAILED', 'SUCCEEDED', 'CANCELLED', 'SUPERSEDED')
|
||||
|
||||
@classmethod
|
||||
def setup_credentials(cls):
|
||||
cls._check_network_config()
|
||||
@@ -142,6 +147,11 @@ class BaseInfraOptimScenarioTest(manager.ScenarioTest):
|
||||
|
||||
return audit.get('state') == 'SUCCEEDED'
|
||||
|
||||
@classmethod
|
||||
def has_audit_finished(cls, audit_uuid):
|
||||
_, audit = cls.client.show_audit(audit_uuid)
|
||||
return audit.get('state') in cls.FINISHED_STATES
|
||||
|
||||
# ### ACTION PLANS ### #
|
||||
|
||||
def delete_action_plan(self, action_plan_uuid):
|
||||
|
||||
@@ -148,24 +148,31 @@ class TestExecuteBasicStrategy(base.BaseInfraOptimScenarioTest):
|
||||
try:
|
||||
self.assertTrue(test.call_until_true(
|
||||
func=functools.partial(
|
||||
self.has_audit_succeeded, audit['uuid']),
|
||||
self.has_audit_finished, audit['uuid']),
|
||||
duration=600,
|
||||
sleep_for=2
|
||||
))
|
||||
except ValueError:
|
||||
self.fail("The audit has failed!")
|
||||
|
||||
_, finished_audit = self.client.show_audit(audit['uuid'])
|
||||
if finished_audit.get('state') in ('FAILED', 'CANCELLED'):
|
||||
self.fail("The audit ended in unexpected state: %s!",
|
||||
finished_audit.get('state'))
|
||||
|
||||
_, action_plans = self.client.list_action_plans(
|
||||
audit_uuid=audit['uuid'])
|
||||
action_plan = action_plans['action_plans'][0]
|
||||
|
||||
_, action_plan = self.client.show_action_plan(action_plan['uuid'])
|
||||
|
||||
if action_plan['state'] in ('SUPERSEDED', 'SUCCEEDED'):
|
||||
# This means the action plan is superseded so we cannot trigger it,
|
||||
# or it is empty.
|
||||
return
|
||||
|
||||
# Execute the action by changing its state to PENDING
|
||||
_, updated_ap = self.client.update_action_plan(
|
||||
action_plan['uuid'],
|
||||
patch=[{'path': '/state', 'op': 'replace', 'value': 'PENDING'}]
|
||||
)
|
||||
_, updated_ap = self.client.start_action_plan(action_plan['uuid'])
|
||||
|
||||
self.assertTrue(test.call_until_true(
|
||||
func=functools.partial(
|
||||
|
||||
@@ -42,21 +42,26 @@ class TestExecuteDummyStrategy(base.BaseInfraOptimScenarioTest):
|
||||
_, audit = self.create_audit(audit_template['uuid'])
|
||||
|
||||
self.assertTrue(test.call_until_true(
|
||||
func=functools.partial(self.has_audit_succeeded, audit['uuid']),
|
||||
func=functools.partial(self.has_audit_finished, audit['uuid']),
|
||||
duration=30,
|
||||
sleep_for=.5
|
||||
))
|
||||
|
||||
self.assertTrue(self.has_audit_succeeded(audit['uuid']))
|
||||
|
||||
_, action_plans = self.client.list_action_plans(
|
||||
audit_uuid=audit['uuid'])
|
||||
action_plan = action_plans['action_plans'][0]
|
||||
|
||||
_, action_plan = self.client.show_action_plan(action_plan['uuid'])
|
||||
|
||||
if action_plan['state'] in ['SUPERSEDED', 'SUCCEEDED']:
|
||||
# This means the action plan is superseded so we cannot trigger it,
|
||||
# or it is empty.
|
||||
return
|
||||
|
||||
# Execute the action by changing its state to PENDING
|
||||
_, updated_ap = self.client.update_action_plan(
|
||||
action_plan['uuid'],
|
||||
patch=[{'path': '/state', 'op': 'replace', 'value': 'PENDING'}]
|
||||
)
|
||||
_, updated_ap = self.client.start_action_plan(action_plan['uuid'])
|
||||
|
||||
self.assertTrue(test.call_until_true(
|
||||
func=functools.partial(
|
||||
|
||||
Reference in New Issue
Block a user