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
|
host=review.openstack.org
|
||||||
port=29418
|
port=29418
|
||||||
project=openstack/watcher.git
|
project=openstack/watcher.git
|
||||||
|
defaultbranch=stable/ocata
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ function init_watcher {
|
|||||||
recreate_database watcher
|
recreate_database watcher
|
||||||
|
|
||||||
# Create watcher schema
|
# 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
|
fi
|
||||||
create_watcher_cache_dir
|
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",
|
"state": "PENDING",
|
||||||
"updated_at": null,
|
"updated_at": null,
|
||||||
"deleted_at": null,
|
"deleted_at": null,
|
||||||
|
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||||
"goal": {
|
"goal": {
|
||||||
"watcher_object.data": {
|
"watcher_object.data": {
|
||||||
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||||
@@ -26,6 +27,7 @@
|
|||||||
},
|
},
|
||||||
"interval": null,
|
"interval": null,
|
||||||
"scope": [],
|
"scope": [],
|
||||||
|
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||||
"strategy": {
|
"strategy": {
|
||||||
"watcher_object.data": {
|
"watcher_object.data": {
|
||||||
"parameters_spec": {
|
"parameters_spec": {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"state": "DELETED",
|
"state": "DELETED",
|
||||||
"updated_at": null,
|
"updated_at": null,
|
||||||
"deleted_at": null,
|
"deleted_at": null,
|
||||||
|
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||||
"goal": {
|
"goal": {
|
||||||
"watcher_object.data": {
|
"watcher_object.data": {
|
||||||
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||||
@@ -26,6 +27,7 @@
|
|||||||
},
|
},
|
||||||
"interval": null,
|
"interval": null,
|
||||||
"scope": [],
|
"scope": [],
|
||||||
|
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||||
"strategy": {
|
"strategy": {
|
||||||
"watcher_object.data": {
|
"watcher_object.data": {
|
||||||
"parameters_spec": {
|
"parameters_spec": {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"updated_at": null,
|
"updated_at": null,
|
||||||
"deleted_at": null,
|
"deleted_at": null,
|
||||||
"fault": null,
|
"fault": null,
|
||||||
|
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||||
"goal": {
|
"goal": {
|
||||||
"watcher_object.data": {
|
"watcher_object.data": {
|
||||||
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||||
@@ -27,6 +28,7 @@
|
|||||||
},
|
},
|
||||||
"interval": null,
|
"interval": null,
|
||||||
"scope": [],
|
"scope": [],
|
||||||
|
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||||
"strategy": {
|
"strategy": {
|
||||||
"watcher_object.data": {
|
"watcher_object.data": {
|
||||||
"parameters_spec": {
|
"parameters_spec": {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
"watcher_object.namespace": "watcher",
|
"watcher_object.namespace": "watcher",
|
||||||
"watcher_object.version": "1.0"
|
"watcher_object.version": "1.0"
|
||||||
},
|
},
|
||||||
|
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||||
"goal": {
|
"goal": {
|
||||||
"watcher_object.data": {
|
"watcher_object.data": {
|
||||||
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||||
@@ -37,6 +38,7 @@
|
|||||||
},
|
},
|
||||||
"interval": null,
|
"interval": null,
|
||||||
"scope": [],
|
"scope": [],
|
||||||
|
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||||
"strategy": {
|
"strategy": {
|
||||||
"watcher_object.data": {
|
"watcher_object.data": {
|
||||||
"parameters_spec": {
|
"parameters_spec": {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"updated_at": null,
|
"updated_at": null,
|
||||||
"deleted_at": null,
|
"deleted_at": null,
|
||||||
"fault": null,
|
"fault": null,
|
||||||
|
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||||
"goal": {
|
"goal": {
|
||||||
"watcher_object.data": {
|
"watcher_object.data": {
|
||||||
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||||
@@ -27,6 +28,7 @@
|
|||||||
},
|
},
|
||||||
"interval": null,
|
"interval": null,
|
||||||
"scope": [],
|
"scope": [],
|
||||||
|
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||||
"strategy": {
|
"strategy": {
|
||||||
"watcher_object.data": {
|
"watcher_object.data": {
|
||||||
"parameters_spec": {
|
"parameters_spec": {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"updated_at": null,
|
"updated_at": null,
|
||||||
"deleted_at": null,
|
"deleted_at": null,
|
||||||
"fault": null,
|
"fault": null,
|
||||||
|
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||||
"goal": {
|
"goal": {
|
||||||
"watcher_object.data": {
|
"watcher_object.data": {
|
||||||
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||||
@@ -27,6 +28,7 @@
|
|||||||
},
|
},
|
||||||
"interval": null,
|
"interval": null,
|
||||||
"scope": [],
|
"scope": [],
|
||||||
|
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||||
"strategy": {
|
"strategy": {
|
||||||
"watcher_object.data": {
|
"watcher_object.data": {
|
||||||
"parameters_spec": {
|
"parameters_spec": {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
"watcher_object.namespace": "watcher",
|
"watcher_object.namespace": "watcher",
|
||||||
"watcher_object.version": "1.0"
|
"watcher_object.version": "1.0"
|
||||||
},
|
},
|
||||||
|
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||||
"goal": {
|
"goal": {
|
||||||
"watcher_object.data": {
|
"watcher_object.data": {
|
||||||
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||||
@@ -37,6 +38,7 @@
|
|||||||
},
|
},
|
||||||
"interval": null,
|
"interval": null,
|
||||||
"scope": [],
|
"scope": [],
|
||||||
|
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||||
"strategy": {
|
"strategy": {
|
||||||
"watcher_object.data": {
|
"watcher_object.data": {
|
||||||
"parameters_spec": {
|
"parameters_spec": {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"updated_at": null,
|
"updated_at": null,
|
||||||
"deleted_at": null,
|
"deleted_at": null,
|
||||||
"fault": null,
|
"fault": null,
|
||||||
|
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||||
"goal": {
|
"goal": {
|
||||||
"watcher_object.data": {
|
"watcher_object.data": {
|
||||||
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||||
@@ -27,6 +28,7 @@
|
|||||||
},
|
},
|
||||||
"interval": null,
|
"interval": null,
|
||||||
"scope": [],
|
"scope": [],
|
||||||
|
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||||
"strategy": {
|
"strategy": {
|
||||||
"watcher_object.data": {
|
"watcher_object.data": {
|
||||||
"parameters_spec": {
|
"parameters_spec": {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"payload": {
|
"payload": {
|
||||||
"watcher_object.name": "AuditUpdatePayload",
|
"watcher_object.name": "AuditUpdatePayload",
|
||||||
"watcher_object.data": {
|
"watcher_object.data": {
|
||||||
|
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||||
"strategy": {
|
"strategy": {
|
||||||
"watcher_object.name": "StrategyPayload",
|
"watcher_object.name": "StrategyPayload",
|
||||||
"watcher_object.data": {
|
"watcher_object.data": {
|
||||||
@@ -36,6 +37,7 @@
|
|||||||
"scope": [],
|
"scope": [],
|
||||||
"created_at": "2016-11-04T16:51:21Z",
|
"created_at": "2016-11-04T16:51:21Z",
|
||||||
"uuid": "f1e0d912-afd9-4bf2-91ef-c99cd08cc1ef",
|
"uuid": "f1e0d912-afd9-4bf2-91ef-c99cd08cc1ef",
|
||||||
|
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||||
"goal": {
|
"goal": {
|
||||||
"watcher_object.name": "GoalPayload",
|
"watcher_object.name": "GoalPayload",
|
||||||
"watcher_object.data": {
|
"watcher_object.data": {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ Overview
|
|||||||
Below you will find a diagram, showing the main components of Watcher:
|
Below you will find a diagram, showing the main components of Watcher:
|
||||||
|
|
||||||
.. image:: ./images/architecture.svg
|
.. image:: ./images/architecture.svg
|
||||||
:width: 100%
|
:width: 110%
|
||||||
|
|
||||||
|
|
||||||
.. _components_definition:
|
.. _components_definition:
|
||||||
@@ -37,13 +37,12 @@ AMQP Bus
|
|||||||
The AMQP message bus handles internal asynchronous communications between the
|
The AMQP message bus handles internal asynchronous communications between the
|
||||||
different Watcher components.
|
different Watcher components.
|
||||||
|
|
||||||
.. _cluster_history_db_definition:
|
.. _cluster_datasource_definition:
|
||||||
|
|
||||||
Cluster History Database
|
Datasource
|
||||||
------------------------
|
----------
|
||||||
|
|
||||||
This component stores the data related to the
|
This component stores the metrics related to the cluster.
|
||||||
:ref:`Cluster History <cluster_history_definition>`.
|
|
||||||
|
|
||||||
It can potentially rely on any appropriate storage system (InfluxDB, OpenTSDB,
|
It can potentially rely on any appropriate storage system (InfluxDB, OpenTSDB,
|
||||||
MongoDB,...) but will probably be more performant when using
|
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
|
which are optimized for handling time series data, which are arrays of numbers
|
||||||
indexed by time (a datetime or a datetime range).
|
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:
|
.. _archi_watcher_api_definition:
|
||||||
|
|
||||||
Watcher API
|
Watcher API
|
||||||
@@ -193,8 +184,8 @@ data:
|
|||||||
:ref:`Managed resources <managed_resource_definition>` (e.g., the data stored
|
: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
|
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 current state of a given :ref:`cluster <cluster_definition>`.
|
||||||
- The data stored in the :ref:`Cluster History Database
|
- The data stored in the :ref:`Cluster Datasource
|
||||||
<cluster_history_db_definition>` which provides information about the past of
|
<cluster_datasource_definition>` which provides information about the past of
|
||||||
the :ref:`Cluster <cluster_definition>`.
|
the :ref:`Cluster <cluster_definition>`.
|
||||||
|
|
||||||
Here below is a sequence diagram showing how the Decision Engine builds and
|
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
|
- **CANCELLED** : the :ref:`Action Plan <action_plan_definition>` was in
|
||||||
**RECOMMENDED**, **PENDING** or **ONGOING** state and was cancelled by the
|
**RECOMMENDED**, **PENDING** or **ONGOING** state and was cancelled by the
|
||||||
:ref:`Administrator <administrator_definition>`
|
: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
|
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
|
.. _`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
|
.. _`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
|
The following code snippet shows how to invoke a Datasource Helper class:
|
||||||
-------------------------------------------
|
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
.. code-block:: py
|
.. code-block:: py
|
||||||
|
|
||||||
# Within your strategy "execute()"
|
from watcher.datasource import ceilometer as ceil
|
||||||
cclient = self.osc.ceilometer
|
from watcher.datasource import monasca as mon
|
||||||
# TODO: Do something here
|
|
||||||
|
@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:
|
Using that you can now query the values for that specific metric:
|
||||||
|
|
||||||
.. code-block:: py
|
.. code-block:: py
|
||||||
|
|
||||||
query = None # e.g. [{'field': 'foo', 'op': 'le', 'value': 34},]
|
if self.config.datasource == "ceilometer":
|
||||||
value_cpu = cclient.samples.list(
|
resource_id = "%s_%s" % (node.uuid, node.hostname)
|
||||||
meter_name='cpu_util',
|
return self.ceilometer.statistic_aggregation(
|
||||||
limit=10, q=query)
|
resource_id=resource_id,
|
||||||
|
meter_name='compute.node.cpu.percent',
|
||||||
|
period="7200",
|
||||||
Read usage metrics using the Watcher Cluster History Helper
|
aggregate='avg',
|
||||||
-----------------------------------------------------------
|
)
|
||||||
|
elif self.config.datasource == "monasca":
|
||||||
Here below is the abstract ``BaseClusterHistory`` class of the Helper.
|
statistics = self.monasca.statistic_aggregation(
|
||||||
|
meter_name='compute.node.cpu.percent',
|
||||||
.. autoclass:: watcher.decision_engine.cluster.history.base.BaseClusterHistory
|
dimensions=dict(hostname=node.uuid),
|
||||||
:members:
|
period=7200,
|
||||||
:noindex:
|
aggregate='avg'
|
||||||
|
)
|
||||||
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'
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -101,12 +101,6 @@ Cluster Data Model (CDM)
|
|||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.model.collector.base
|
.. watcher-term:: watcher.decision_engine.model.collector.base
|
||||||
|
|
||||||
.. _cluster_history_definition:
|
|
||||||
|
|
||||||
Cluster History
|
|
||||||
===============
|
|
||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.cluster.history.base
|
|
||||||
|
|
||||||
.. _controller_node_definition:
|
.. _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
|
SUCCEEDED --> DELETED : Administrator removes\nAction Plan
|
||||||
ONGOING --> CANCELLED : Administrator cancels\nAction Plan
|
ONGOING --> CANCELLED : Administrator cancels\nAction Plan
|
||||||
RECOMMENDED --> 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
|
PENDING --> CANCELLED : Administrator cancels\nAction Plan
|
||||||
CANCELLED --> DELETED
|
CANCELLED --> DELETED
|
||||||
|
SUPERSEDED --> DELETED
|
||||||
DELETED --> [*]
|
DELETED --> [*]
|
||||||
|
|
||||||
@enduml
|
@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
|
interval : Integer, nullable
|
||||||
parameters : JSONEncodedDict, nullable
|
parameters : JSONEncodedDict, nullable
|
||||||
scope : JSONEncodedList, nullable
|
scope : JSONEncodedList, nullable
|
||||||
|
auto_trigger: Boolean
|
||||||
|
|
||||||
created_at : DateTime
|
created_at : DateTime
|
||||||
updated_at : DateTime
|
updated_at : DateTime
|
||||||
@@ -73,7 +74,6 @@ table(action_plans) {
|
|||||||
foreign_key("audit_id : Integer, nullable")
|
foreign_key("audit_id : Integer, nullable")
|
||||||
foreign_key("strategy_id : Integer")
|
foreign_key("strategy_id : Integer")
|
||||||
uuid : String[36]
|
uuid : String[36]
|
||||||
first_action_id : Integer
|
|
||||||
state : String[20], nullable
|
state : String[20], nullable
|
||||||
global_efficacy : JSONEncodedDict, nullable
|
global_efficacy : JSONEncodedDict, nullable
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ table(actions) {
|
|||||||
action_type : String[255]
|
action_type : String[255]
|
||||||
input_parameters : JSONEncodedDict, nullable
|
input_parameters : JSONEncodedDict, nullable
|
||||||
state : String[20], nullable
|
state : String[20], nullable
|
||||||
next : String[36], nullable
|
parents : JSONEncodedList, nullable
|
||||||
|
|
||||||
created_at : DateTime
|
created_at : DateTime
|
||||||
updated_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]
|
[testenv]
|
||||||
usedevelop = True
|
usedevelop = True
|
||||||
whitelist_externals = find
|
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 =
|
setenv =
|
||||||
VIRTUAL_ENV={envdir}
|
VIRTUAL_ENV={envdir}
|
||||||
deps = -r{toxinidir}/test-requirements.txt
|
deps = -r{toxinidir}/test-requirements.txt
|
||||||
|
|||||||
@@ -455,7 +455,8 @@ class ActionPlansController(rest.RestController):
|
|||||||
:param action_plan_uuid: UUID of a action.
|
:param action_plan_uuid: UUID of a action.
|
||||||
"""
|
"""
|
||||||
context = pecan.request.context
|
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,
|
policy.enforce(context, 'action_plan:delete', action_plan,
|
||||||
action='action_plan:delete')
|
action='action_plan:delete')
|
||||||
|
|
||||||
@@ -474,8 +475,8 @@ class ActionPlansController(rest.RestController):
|
|||||||
raise exception.OperationNotPermitted
|
raise exception.OperationNotPermitted
|
||||||
|
|
||||||
context = pecan.request.context
|
context = pecan.request.context
|
||||||
action_plan_to_update = api_utils.get_resource('ActionPlan',
|
action_plan_to_update = api_utils.get_resource(
|
||||||
action_plan_uuid)
|
'ActionPlan', action_plan_uuid, eager=True)
|
||||||
policy.enforce(context, 'action_plan:update', action_plan_to_update,
|
policy.enforce(context, 'action_plan:update', action_plan_to_update,
|
||||||
action='action_plan:update')
|
action='action_plan:update')
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,21 @@ from watcher.decision_engine import rpcapi
|
|||||||
from watcher import objects
|
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):
|
class AuditPostType(wtypes.Base):
|
||||||
|
|
||||||
audit_template_uuid = wtypes.wsattr(types.uuid, mandatory=False)
|
audit_template_uuid = wtypes.wsattr(types.uuid, mandatory=False)
|
||||||
@@ -561,6 +576,17 @@ class AuditsController(rest.RestController):
|
|||||||
except api_utils.JSONPATCH_EXCEPTIONS as e:
|
except api_utils.JSONPATCH_EXCEPTIONS as e:
|
||||||
raise exception.PatchError(patch=patch, reason=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
|
# Update only the fields that have changed
|
||||||
for field in objects.Audit.fields:
|
for field in objects.Audit.fields:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -333,6 +333,7 @@ class AuditTemplate(base.APIBase):
|
|||||||
|
|
||||||
self.fields.append('goal_id')
|
self.fields.append('goal_id')
|
||||||
self.fields.append('strategy_id')
|
self.fields.append('strategy_id')
|
||||||
|
setattr(self, 'strategy_id', kwargs.get('strategy_id', wtypes.Unset))
|
||||||
|
|
||||||
# goal_uuid & strategy_uuid are not part of
|
# goal_uuid & strategy_uuid are not part of
|
||||||
# objects.AuditTemplate.fields because they're API-only attributes.
|
# 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))
|
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):
|
def as_filters_dict(**filters):
|
||||||
filters_dict = {}
|
filters_dict = {}
|
||||||
for filter_name, filter_value in filters.items():
|
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.action_plan import base
|
||||||
from watcher.applier import default
|
from watcher.applier import default
|
||||||
|
from watcher import notifications
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
|
from watcher.objects import fields
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DefaultActionPlanHandler(base.BaseActionPlanHandler):
|
class DefaultActionPlanHandler(base.BaseActionPlanHandler):
|
||||||
|
|
||||||
def __init__(self, context, service, action_plan_uuid):
|
def __init__(self, context, service, action_plan_uuid):
|
||||||
super(DefaultActionPlanHandler, self).__init__()
|
super(DefaultActionPlanHandler, self).__init__()
|
||||||
self.ctx = context
|
self.ctx = context
|
||||||
self.service = service
|
self.service = service
|
||||||
self.action_plan_uuid = action_plan_uuid
|
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):
|
def execute(self):
|
||||||
try:
|
try:
|
||||||
self.update_action_plan(self.action_plan_uuid,
|
action_plan = objects.ActionPlan.get_by_uuid(
|
||||||
objects.action_plan.State.ONGOING)
|
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 = default.DefaultApplier(self.ctx, self.service)
|
||||||
applier.execute(self.action_plan_uuid)
|
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:
|
except Exception as e:
|
||||||
LOG.exception(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:
|
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):
|
def execute(self, action_plan_uuid):
|
||||||
LOG.debug("Executing action plan %s ", action_plan_uuid)
|
LOG.debug("Executing action plan %s ", action_plan_uuid)
|
||||||
|
|
||||||
filters = {'action_plan_uuid': action_plan_uuid}
|
filters = {'action_plan_uuid': action_plan_uuid}
|
||||||
actions = objects.Action.list(self.context,
|
actions = objects.Action.list(self.context, filters=filters)
|
||||||
filters=filters)
|
|
||||||
return self.engine.execute(actions)
|
return self.engine.execute(actions)
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ def add_command_parsers(subparsers):
|
|||||||
"Optionally, use --revision to specify an alembic revision "
|
"Optionally, use --revision to specify an alembic revision "
|
||||||
"string to upgrade to.")
|
"string to upgrade to.")
|
||||||
parser.set_defaults(func=DBCommand.upgrade)
|
parser.set_defaults(func=DBCommand.upgrade)
|
||||||
parser.add_argument('--revision', nargs='?')
|
parser.add_argument('revision', nargs='?')
|
||||||
|
|
||||||
parser = subparsers.add_parser(
|
parser = subparsers.add_parser(
|
||||||
'downgrade',
|
'downgrade',
|
||||||
@@ -79,10 +79,10 @@ def add_command_parsers(subparsers):
|
|||||||
"While optional, one should generally use --revision to "
|
"While optional, one should generally use --revision to "
|
||||||
"specify the alembic revision string to downgrade to.")
|
"specify the alembic revision string to downgrade to.")
|
||||||
parser.set_defaults(func=DBCommand.downgrade)
|
parser.set_defaults(func=DBCommand.downgrade)
|
||||||
parser.add_argument('--revision', nargs='?')
|
parser.add_argument('revision', nargs='?')
|
||||||
|
|
||||||
parser = subparsers.add_parser('stamp')
|
parser = subparsers.add_parser('stamp')
|
||||||
parser.add_argument('--revision', nargs='?')
|
parser.add_argument('revision', nargs='?')
|
||||||
parser.set_defaults(func=DBCommand.stamp)
|
parser.set_defaults(func=DBCommand.stamp)
|
||||||
|
|
||||||
parser = subparsers.add_parser(
|
parser = subparsers.add_parser(
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class RequestContext(context.RequestContext):
|
|||||||
self.domain_name = domain_name
|
self.domain_name = domain_name
|
||||||
self.domain_id = domain_id
|
self.domain_id = domain_id
|
||||||
self.auth_token_info = auth_token_info
|
self.auth_token_info = auth_token_info
|
||||||
self.user_id = user_id
|
self.user_id = user_id or user
|
||||||
self.project_id = project_id
|
self.project_id = project_id
|
||||||
if not timestamp:
|
if not timestamp:
|
||||||
timestamp = timeutils.utcnow()
|
timestamp = timeutils.utcnow()
|
||||||
|
|||||||
@@ -174,6 +174,14 @@ class EagerlyLoadedAuditRequired(InvalidAudit):
|
|||||||
msg_fmt = _("Audit %(audit)s was not eagerly loaded")
|
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):
|
class InvalidUUID(Invalid):
|
||||||
msg_fmt = _("Expected a uuid but received %(uuid)s")
|
msg_fmt = _("Expected a uuid but received %(uuid)s")
|
||||||
|
|
||||||
@@ -402,11 +410,15 @@ class WildcardCharacterIsUsed(WatcherException):
|
|||||||
|
|
||||||
# Model
|
# 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")
|
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")
|
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'
|
revision = revision or 'head'
|
||||||
config = config or _alembic_config()
|
config = config or _alembic_config()
|
||||||
|
|
||||||
alembic.command.upgrade(config, revision or 'head')
|
alembic.command.upgrade(config, revision)
|
||||||
|
|
||||||
|
|
||||||
def create_schema(config=None, engine=None):
|
def create_schema(config=None, engine=None):
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ class AuditHandler(BaseAuditHandler):
|
|||||||
solution = self.do_execute(audit, request_context)
|
solution = self.do_execute(audit, request_context)
|
||||||
self.post_execute(audit, solution, request_context)
|
self.post_execute(audit, solution, request_context)
|
||||||
except exception.ActionPlanIsOngoing as e:
|
except exception.ActionPlanIsOngoing as e:
|
||||||
LOG.exception(e)
|
LOG.warning(e)
|
||||||
if audit.audit_type == objects.audit.AuditType.ONESHOT.value:
|
if audit.audit_type == objects.audit.AuditType.ONESHOT.value:
|
||||||
self.update_audit_state(audit, objects.audit.State.CANCELLED)
|
self.update_audit_state(audit, objects.audit.State.CANCELLED)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -215,8 +215,7 @@ class ModelBuilder(object):
|
|||||||
compute_node = self.model.get_node_by_uuid(
|
compute_node = self.model.get_node_by_uuid(
|
||||||
cnode_uuid)
|
cnode_uuid)
|
||||||
# Connect the instance to its compute node
|
# Connect the instance to its compute node
|
||||||
self.model.add_edge(
|
self.model.map_instance(instance, compute_node)
|
||||||
instance, compute_node, label='RUNS_ON')
|
|
||||||
except exception.ComputeNodeNotFound:
|
except exception.ComputeNodeNotFound:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ LOG = log.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class Element(base.WatcherObject, base.WatcherObjectDictCompat):
|
class Element(base.WatcherObject, base.WatcherObjectDictCompat,
|
||||||
|
base.WatcherComparableObject):
|
||||||
|
|
||||||
# Initial version
|
# Initial version
|
||||||
VERSION = '1.0'
|
VERSION = '1.0'
|
||||||
|
|||||||
@@ -57,13 +57,13 @@ class ModelRoot(nx.DiGraph, base.Model):
|
|||||||
@lockutils.synchronized("model_root")
|
@lockutils.synchronized("model_root")
|
||||||
def add_node(self, node):
|
def add_node(self, node):
|
||||||
self.assert_node(node)
|
self.assert_node(node)
|
||||||
super(ModelRoot, self).add_node(node)
|
super(ModelRoot, self).add_node(node.uuid, node)
|
||||||
|
|
||||||
@lockutils.synchronized("model_root")
|
@lockutils.synchronized("model_root")
|
||||||
def remove_node(self, node):
|
def remove_node(self, node):
|
||||||
self.assert_node(node)
|
self.assert_node(node)
|
||||||
try:
|
try:
|
||||||
super(ModelRoot, self).remove_node(node)
|
super(ModelRoot, self).remove_node(node.uuid)
|
||||||
except nx.NetworkXError as exc:
|
except nx.NetworkXError as exc:
|
||||||
LOG.exception(exc)
|
LOG.exception(exc)
|
||||||
raise exception.ComputeNodeNotFound(name=node.uuid)
|
raise exception.ComputeNodeNotFound(name=node.uuid)
|
||||||
@@ -72,7 +72,7 @@ class ModelRoot(nx.DiGraph, base.Model):
|
|||||||
def add_instance(self, instance):
|
def add_instance(self, instance):
|
||||||
self.assert_instance(instance)
|
self.assert_instance(instance)
|
||||||
try:
|
try:
|
||||||
super(ModelRoot, self).add_node(instance)
|
super(ModelRoot, self).add_node(instance.uuid, instance)
|
||||||
except nx.NetworkXError as exc:
|
except nx.NetworkXError as exc:
|
||||||
LOG.exception(exc)
|
LOG.exception(exc)
|
||||||
raise exception.InstanceNotFound(name=instance.uuid)
|
raise exception.InstanceNotFound(name=instance.uuid)
|
||||||
@@ -80,7 +80,7 @@ class ModelRoot(nx.DiGraph, base.Model):
|
|||||||
@lockutils.synchronized("model_root")
|
@lockutils.synchronized("model_root")
|
||||||
def remove_instance(self, instance):
|
def remove_instance(self, instance):
|
||||||
self.assert_instance(instance)
|
self.assert_instance(instance)
|
||||||
super(ModelRoot, self).remove_node(instance)
|
super(ModelRoot, self).remove_node(instance.uuid)
|
||||||
|
|
||||||
@lockutils.synchronized("model_root")
|
@lockutils.synchronized("model_root")
|
||||||
def map_instance(self, instance, node):
|
def map_instance(self, instance, node):
|
||||||
@@ -98,7 +98,7 @@ class ModelRoot(nx.DiGraph, base.Model):
|
|||||||
self.assert_node(node)
|
self.assert_node(node)
|
||||||
self.assert_instance(instance)
|
self.assert_instance(instance)
|
||||||
|
|
||||||
self.add_edge(instance, node)
|
self.add_edge(instance.uuid, node.uuid)
|
||||||
|
|
||||||
@lockutils.synchronized("model_root")
|
@lockutils.synchronized("model_root")
|
||||||
def unmap_instance(self, instance, node):
|
def unmap_instance(self, instance, node):
|
||||||
@@ -107,7 +107,7 @@ class ModelRoot(nx.DiGraph, base.Model):
|
|||||||
if isinstance(node, six.string_types):
|
if isinstance(node, six.string_types):
|
||||||
node = self.get_node_by_uuid(node)
|
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):
|
def delete_instance(self, instance, node=None):
|
||||||
self.assert_instance(instance)
|
self.assert_instance(instance)
|
||||||
@@ -130,55 +130,59 @@ class ModelRoot(nx.DiGraph, base.Model):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# unmap
|
# unmap
|
||||||
self.remove_edge(instance, source_node)
|
self.remove_edge(instance.uuid, source_node.uuid)
|
||||||
# map
|
# map
|
||||||
self.add_edge(instance, destination_node)
|
self.add_edge(instance.uuid, destination_node.uuid)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@lockutils.synchronized("model_root")
|
@lockutils.synchronized("model_root")
|
||||||
def get_all_compute_nodes(self):
|
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)}
|
if isinstance(cn, element.ComputeNode)}
|
||||||
|
|
||||||
@lockutils.synchronized("model_root")
|
@lockutils.synchronized("model_root")
|
||||||
def get_node_by_uuid(self, uuid):
|
def get_node_by_uuid(self, uuid):
|
||||||
for graph_node in self.nodes():
|
try:
|
||||||
if (isinstance(graph_node, element.ComputeNode) and
|
return self._get_by_uuid(uuid)
|
||||||
graph_node.uuid == uuid):
|
except exception.ComputeResourceNotFound:
|
||||||
return graph_node
|
raise exception.ComputeNodeNotFound(name=uuid)
|
||||||
raise exception.ComputeNodeNotFound(name=uuid)
|
|
||||||
|
|
||||||
@lockutils.synchronized("model_root")
|
@lockutils.synchronized("model_root")
|
||||||
def get_instance_by_uuid(self, uuid):
|
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):
|
def _get_by_uuid(self, uuid):
|
||||||
for graph_node in self.nodes():
|
try:
|
||||||
if (isinstance(graph_node, element.Instance) and
|
return self.node[uuid]
|
||||||
graph_node.uuid == str(uuid)):
|
except Exception as exc:
|
||||||
return graph_node
|
LOG.exception(exc)
|
||||||
raise exception.InstanceNotFound(name=uuid)
|
raise exception.ComputeResourceNotFound(name=uuid)
|
||||||
|
|
||||||
@lockutils.synchronized("model_root")
|
@lockutils.synchronized("model_root")
|
||||||
def get_node_by_instance_uuid(self, instance_uuid):
|
def get_node_by_instance_uuid(self, instance_uuid):
|
||||||
instance = self._get_instance_by_uuid(instance_uuid)
|
instance = self._get_by_uuid(instance_uuid)
|
||||||
for node in self.neighbors(instance):
|
for node_uuid in self.neighbors(instance.uuid):
|
||||||
|
node = self._get_by_uuid(node_uuid)
|
||||||
if isinstance(node, element.ComputeNode):
|
if isinstance(node, element.ComputeNode):
|
||||||
return node
|
return node
|
||||||
raise exception.ComputeNodeNotFound(name=instance_uuid)
|
raise exception.ComputeNodeNotFound(name=instance_uuid)
|
||||||
|
|
||||||
@lockutils.synchronized("model_root")
|
@lockutils.synchronized("model_root")
|
||||||
def get_all_instances(self):
|
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)}
|
if isinstance(inst, element.Instance)}
|
||||||
|
|
||||||
@lockutils.synchronized("model_root")
|
@lockutils.synchronized("model_root")
|
||||||
def get_node_instances(self, node):
|
def get_node_instances(self, node):
|
||||||
self.assert_node(node)
|
self.assert_node(node)
|
||||||
node_instances = []
|
node_instances = []
|
||||||
for neighbor in self.predecessors(node):
|
for instance_uuid in self.predecessors(node.uuid):
|
||||||
if isinstance(neighbor, element.Instance):
|
instance = self._get_by_uuid(instance_uuid)
|
||||||
node_instances.append(neighbor)
|
if isinstance(instance, element.Instance):
|
||||||
|
node_instances.append(instance)
|
||||||
|
|
||||||
return node_instances
|
return node_instances
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class WeightPlanner(base.BasePlanner):
|
|||||||
*Limitations*
|
*Limitations*
|
||||||
|
|
||||||
- This planner requires to have action_weights and parallelization configs
|
- This planner requires to have action_weights and parallelization configs
|
||||||
tuned well.
|
tuned well.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ class DefaultScope(base.BaseScope):
|
|||||||
try:
|
try:
|
||||||
node_name = cluster_model.get_node_by_instance_uuid(
|
node_name = cluster_model.get_node_by_instance_uuid(
|
||||||
instance_uuid).uuid
|
instance_uuid).uuid
|
||||||
except exception.InstanceNotFound:
|
except exception.ComputeResourceNotFound:
|
||||||
LOG.warning(_LW("The following instance %s cannot be found. "
|
LOG.warning(_LW("The following instance %s cannot be found. "
|
||||||
"It might be deleted from CDM along with node"
|
"It might be deleted from CDM along with node"
|
||||||
" instance was hosted on."),
|
" instance was hosted on."),
|
||||||
|
|||||||
@@ -415,11 +415,11 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
sorted_score):
|
sorted_score):
|
||||||
number_migrations = 0
|
number_migrations = 0
|
||||||
for mig_instance, __ in sorted_instances:
|
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(
|
mig_source_node = self.compute_model.get_node_by_uuid(
|
||||||
node_to_release)
|
node_to_release)
|
||||||
mig_destination_node = self.compute_model.get_node_by_uuid(
|
mig_destination_node = self.compute_model.get_node_by_uuid(
|
||||||
sorted_score[j][0])
|
node_uuid)
|
||||||
|
|
||||||
result = self.check_migration(
|
result = self.check_migration(
|
||||||
mig_source_node, mig_destination_node, mig_instance)
|
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():
|
for strategy_id, synced_strategy in self.strategy_mapping.items():
|
||||||
filters = {"strategy_id": strategy_id}
|
filters = {"strategy_id": strategy_id}
|
||||||
stale_action_plans = objects.ActionPlan.list(
|
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)
|
# Update strategy IDs for all stale action plans (w/o saving)
|
||||||
for action_plan in stale_action_plans:
|
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():
|
for audit_id, synced_audit in self.stale_audits_map.items():
|
||||||
filters = {"audit_id": audit_id}
|
filters = {"audit_id": audit_id}
|
||||||
stale_action_plans = objects.ActionPlan.list(
|
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)
|
# Update audit IDs for all stale action plans (w/o saving)
|
||||||
for action_plan in stale_action_plans:
|
for action_plan in stale_action_plans:
|
||||||
@@ -448,7 +448,7 @@ class Syncer(object):
|
|||||||
audit.id].state = objects.audit.State.CANCELLED
|
audit.id].state = objects.audit.State.CANCELLED
|
||||||
|
|
||||||
stale_action_plans = objects.ActionPlan.list(
|
stale_action_plans = objects.ActionPlan.list(
|
||||||
self.ctx, filters=filters)
|
self.ctx, filters=filters, eager=True)
|
||||||
for action_plan in stale_action_plans:
|
for action_plan in stale_action_plans:
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
_LW("Action Plan '%(action_plan)s' references a "
|
_LW("Action Plan '%(action_plan)s' references a "
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
# need to be changed after we moved these function inside the package
|
# need to be changed after we moved these function inside the package
|
||||||
# Todo(gibi): remove these imports after legacy notifications using these are
|
# Todo(gibi): remove these imports after legacy notifications using these are
|
||||||
# transformed to versioned notifications
|
# transformed to versioned notifications
|
||||||
|
from watcher.notifications import action_plan # noqa
|
||||||
from watcher.notifications import audit # noqa
|
from watcher.notifications import audit # noqa
|
||||||
from watcher.notifications import exception # noqa
|
from watcher.notifications import exception # noqa
|
||||||
from watcher.notifications import goal # 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
|
@base.WatcherObjectRegistry.register_notification
|
||||||
class AuditPayload(notificationbase.NotificationPayloadBase):
|
class TerseAuditPayload(notificationbase.NotificationPayloadBase):
|
||||||
SCHEMA = {
|
SCHEMA = {
|
||||||
'uuid': ('audit', 'uuid'),
|
'uuid': ('audit', 'uuid'),
|
||||||
|
|
||||||
@@ -57,19 +57,54 @@ class AuditPayload(notificationbase.NotificationPayloadBase):
|
|||||||
'scope': wfields.FlexibleListOfDictField(nullable=True),
|
'scope': wfields.FlexibleListOfDictField(nullable=True),
|
||||||
'goal_uuid': wfields.UUIDField(),
|
'goal_uuid': wfields.UUIDField(),
|
||||||
'strategy_uuid': wfields.UUIDField(nullable=True),
|
'strategy_uuid': wfields.UUIDField(nullable=True),
|
||||||
'goal': wfields.ObjectField('GoalPayload'),
|
|
||||||
'strategy': wfields.ObjectField('StrategyPayload', nullable=True),
|
|
||||||
|
|
||||||
'created_at': wfields.DateTimeField(nullable=True),
|
'created_at': wfields.DateTimeField(nullable=True),
|
||||||
'updated_at': wfields.DateTimeField(nullable=True),
|
'updated_at': wfields.DateTimeField(nullable=True),
|
||||||
'deleted_at': wfields.DateTimeField(nullable=True),
|
'deleted_at': wfields.DateTimeField(nullable=True),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, audit, **kwargs):
|
def __init__(self, audit, goal_uuid, strategy_uuid=None, **kwargs):
|
||||||
super(AuditPayload, self).__init__(**kwargs)
|
super(TerseAuditPayload, self).__init__(
|
||||||
|
goal_uuid=goal_uuid, strategy_uuid=strategy_uuid, **kwargs)
|
||||||
self.populate_schema(audit=audit)
|
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
|
@base.WatcherObjectRegistry.register_notification
|
||||||
class AuditStateUpdatePayload(notificationbase.NotificationPayloadBase):
|
class AuditStateUpdatePayload(notificationbase.NotificationPayloadBase):
|
||||||
# Version 1.0: Initial version
|
# Version 1.0: Initial version
|
||||||
@@ -91,6 +126,7 @@ class AuditCreatePayload(AuditPayload):
|
|||||||
super(AuditCreatePayload, self).__init__(
|
super(AuditCreatePayload, self).__init__(
|
||||||
audit=audit,
|
audit=audit,
|
||||||
goal=goal,
|
goal=goal,
|
||||||
|
goal_uuid=goal.uuid,
|
||||||
strategy=strategy)
|
strategy=strategy)
|
||||||
|
|
||||||
|
|
||||||
@@ -107,6 +143,7 @@ class AuditUpdatePayload(AuditPayload):
|
|||||||
audit=audit,
|
audit=audit,
|
||||||
state_update=state_update,
|
state_update=state_update,
|
||||||
goal=goal,
|
goal=goal,
|
||||||
|
goal_uuid=goal.uuid,
|
||||||
strategy=strategy)
|
strategy=strategy)
|
||||||
|
|
||||||
|
|
||||||
@@ -122,6 +159,7 @@ class AuditActionPayload(AuditPayload):
|
|||||||
super(AuditActionPayload, self).__init__(
|
super(AuditActionPayload, self).__init__(
|
||||||
audit=audit,
|
audit=audit,
|
||||||
goal=goal,
|
goal=goal,
|
||||||
|
goal_uuid=goal.uuid,
|
||||||
strategy=strategy,
|
strategy=strategy,
|
||||||
**kwargs)
|
**kwargs)
|
||||||
|
|
||||||
@@ -136,6 +174,7 @@ class AuditDeletePayload(AuditPayload):
|
|||||||
super(AuditDeletePayload, self).__init__(
|
super(AuditDeletePayload, self).__init__(
|
||||||
audit=audit,
|
audit=audit,
|
||||||
goal=goal,
|
goal=goal,
|
||||||
|
goal_uuid=goal.uuid,
|
||||||
strategy=strategy)
|
strategy=strategy)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log
|
||||||
|
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common import rpc
|
from watcher.common import rpc
|
||||||
@@ -20,6 +21,7 @@ from watcher.objects import base
|
|||||||
from watcher.objects import fields as wfields
|
from watcher.objects import fields as wfields
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
# Definition of notification levels in increasing order of severity
|
# Definition of notification levels in increasing order of severity
|
||||||
NOTIFY_LEVELS = {
|
NOTIFY_LEVELS = {
|
||||||
@@ -59,7 +61,8 @@ class EventType(NotificationObject):
|
|||||||
# Version 1.0: Initial version
|
# Version 1.0: Initial version
|
||||||
# Version 1.1: Added STRATEGY action in NotificationAction enum
|
# Version 1.1: Added STRATEGY action in NotificationAction enum
|
||||||
# Version 1.2: Added PLANNER 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 = {
|
fields = {
|
||||||
'object': wfields.StringField(),
|
'object': wfields.StringField(),
|
||||||
@@ -171,6 +174,7 @@ class NotificationBase(NotificationObject):
|
|||||||
def _emit(self, context, event_type, publisher_id, payload):
|
def _emit(self, context, event_type, publisher_id, payload):
|
||||||
notifier = rpc.get_notifier(publisher_id)
|
notifier = rpc.get_notifier(publisher_id)
|
||||||
notify = getattr(notifier, self.priority)
|
notify = getattr(notifier, self.priority)
|
||||||
|
LOG.debug("Emitting notification `%s`", event_type)
|
||||||
notify(context, event_type=event_type, payload=payload)
|
notify(context, event_type=event_type, payload=payload)
|
||||||
|
|
||||||
def emit(self, context):
|
def emit(self, context):
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ state may be one of the following:
|
|||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common import utils
|
from watcher.common import utils
|
||||||
from watcher.db import api as db_api
|
from watcher.db import api as db_api
|
||||||
|
from watcher import notifications
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
from watcher.objects import base
|
from watcher.objects import base
|
||||||
from watcher.objects import fields as wfields
|
from watcher.objects import fields as wfields
|
||||||
@@ -117,6 +118,39 @@ class ActionPlan(base.WatcherPersistentObject, base.WatcherObject,
|
|||||||
'strategy': (objects.Strategy, 'strategy_id'),
|
'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
|
@base.remotable_classmethod
|
||||||
def get(cls, context, action_plan_id, eager=False):
|
def get(cls, context, action_plan_id, eager=False):
|
||||||
"""Find a action_plan based on its id or uuid and return a Action object.
|
"""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
|
# notifications containing information about the related relationships
|
||||||
self._from_db_object(self, db_action_plan, eager=True)
|
self._from_db_object(self, db_action_plan, eager=True)
|
||||||
|
|
||||||
|
def _notify():
|
||||||
|
notifications.action_plan.send_create(self._context, self)
|
||||||
|
|
||||||
|
_notify()
|
||||||
|
|
||||||
@base.remotable
|
@base.remotable
|
||||||
def destroy(self):
|
def destroy(self):
|
||||||
"""Delete the action plan from the DB"""
|
"""Delete the action plan from the DB"""
|
||||||
@@ -221,8 +260,16 @@ class ActionPlan(base.WatcherPersistentObject, base.WatcherObject,
|
|||||||
"""
|
"""
|
||||||
updates = self.obj_get_changes()
|
updates = self.obj_get_changes()
|
||||||
db_obj = self.dbapi.update_action_plan(self.uuid, updates)
|
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)
|
self.obj_refresh(obj)
|
||||||
|
|
||||||
|
def _notify():
|
||||||
|
notifications.action_plan.send_update(
|
||||||
|
self._context, self, old_state=self.old_state)
|
||||||
|
|
||||||
|
_notify()
|
||||||
|
|
||||||
self.obj_reset_changes()
|
self.obj_reset_changes()
|
||||||
|
|
||||||
@base.remotable
|
@base.remotable
|
||||||
@@ -262,3 +309,8 @@ class ActionPlan(base.WatcherPersistentObject, base.WatcherObject,
|
|||||||
obj = self._from_db_object(
|
obj = self._from_db_object(
|
||||||
self.__class__(self._context), db_obj, eager=False)
|
self.__class__(self._context), db_obj, eager=False)
|
||||||
self.obj_refresh(obj)
|
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
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class WatcherComparableObject(ovo_base.ComparableVersionedObject):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class WatcherPersistentObject(object):
|
class WatcherPersistentObject(object):
|
||||||
"""Mixin class for Persistent objects.
|
"""Mixin class for Persistent objects.
|
||||||
|
|
||||||
|
|||||||
@@ -128,8 +128,9 @@ class NotificationAction(BaseWatcherEnum):
|
|||||||
|
|
||||||
STRATEGY = 'strategy'
|
STRATEGY = 'strategy'
|
||||||
PLANNER = 'planner'
|
PLANNER = 'planner'
|
||||||
|
EXECUTION = 'execution'
|
||||||
|
|
||||||
ALL = (CREATE, UPDATE, EXCEPTION, DELETE, STRATEGY, PLANNER)
|
ALL = (CREATE, UPDATE, EXCEPTION, DELETE, STRATEGY, PLANNER, EXECUTION)
|
||||||
|
|
||||||
|
|
||||||
class NotificationPriorityField(BaseEnumField):
|
class NotificationPriorityField(BaseEnumField):
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import itertools
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
@@ -267,7 +268,7 @@ class TestPatch(api_base.FunctionalTest):
|
|||||||
test_time = datetime.datetime(2000, 1, 1, 0, 0)
|
test_time = datetime.datetime(2000, 1, 1, 0, 0)
|
||||||
mock_utcnow.return_value = test_time
|
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)
|
response = self.get_json('/audits/%s' % self.audit.uuid)
|
||||||
self.assertNotEqual(new_state, response['state'])
|
self.assertNotEqual(new_state, response['state'])
|
||||||
|
|
||||||
@@ -343,6 +344,128 @@ class TestPatch(api_base.FunctionalTest):
|
|||||||
self.assertTrue(response.json['error_message'])
|
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):
|
class TestPost(api_base.FunctionalTest):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|||||||
@@ -18,6 +18,9 @@
|
|||||||
import mock
|
import mock
|
||||||
|
|
||||||
from watcher.applier.action_plan import default
|
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.objects import action_plan as ap_objects
|
||||||
from watcher.tests.db import base
|
from watcher.tests.db import base
|
||||||
from watcher.tests.objects import utils as obj_utils
|
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 TestDefaultActionPlanHandler(base.DbTestCase):
|
||||||
|
|
||||||
|
class FakeApplierException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestDefaultActionPlanHandler, self).setUp()
|
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_goal(self.context)
|
||||||
obj_utils.create_test_strategy(self.context)
|
obj_utils.create_test_strategy(self.context)
|
||||||
obj_utils.create_test_audit(self.context)
|
obj_utils.create_test_audit(self.context)
|
||||||
self.action_plan = obj_utils.create_test_action_plan(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(
|
command = default.DefaultActionPlanHandler(
|
||||||
self.context, mock.MagicMock(), self.action_plan.uuid)
|
self.context, mock.MagicMock(), self.action_plan.uuid)
|
||||||
command.execute()
|
command.execute()
|
||||||
action_plan = ap_objects.ActionPlan.get_by_uuid(
|
|
||||||
self.context, self.action_plan.uuid)
|
expected_calls = [
|
||||||
self.assertEqual(ap_objects.State.SUCCEEDED, action_plan.state)
|
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'),
|
'updated_at': kwargs.get('updated_at'),
|
||||||
'deleted_at': kwargs.get('deleted_at'),
|
'deleted_at': kwargs.get('deleted_at'),
|
||||||
'parameters': kwargs.get('parameters', {}),
|
'parameters': kwargs.get('parameters', {}),
|
||||||
'interval': kwargs.get('period', 3600),
|
'interval': kwargs.get('interval', 3600),
|
||||||
'goal_id': kwargs.get('goal_id', 1),
|
'goal_id': kwargs.get('goal_id', 1),
|
||||||
'strategy_id': kwargs.get('strategy_id', None),
|
'strategy_id': kwargs.get('strategy_id', None),
|
||||||
'scope': kwargs.get('scope', []),
|
'scope': kwargs.get('scope', []),
|
||||||
|
|||||||
@@ -175,12 +175,19 @@ class TestAutoTriggerActionPlan(base.DbTestCase):
|
|||||||
self.ongoing_action_plan = obj_utils.create_test_action_plan(
|
self.ongoing_action_plan = obj_utils.create_test_action_plan(
|
||||||
self.context,
|
self.context,
|
||||||
uuid=uuidutils.generate_uuid(),
|
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.recommended_action_plan = obj_utils.create_test_action_plan(
|
||||||
self.context,
|
self.context,
|
||||||
uuid=uuidutils.generate_uuid(),
|
uuid=uuidutils.generate_uuid(),
|
||||||
state=objects.action_plan.State.ONGOING,
|
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')
|
@mock.patch.object(objects.action_plan.ActionPlan, 'list')
|
||||||
|
|||||||
@@ -153,7 +153,6 @@ class FakeMonascaMetrics(object):
|
|||||||
# measurements[uuid] = random.randint(1, 4)
|
# measurements[uuid] = random.randint(1, 4)
|
||||||
measurements[uuid] = 8
|
measurements[uuid] = 8
|
||||||
|
|
||||||
# import ipdb; ipdb.set_trace()
|
|
||||||
return [{'columns': ['avg'],
|
return [{'columns': ['avg'],
|
||||||
'statistics': [[float(measurements[str(uuid)])]]}]
|
'statistics': [[float(measurements[str(uuid)])]]}]
|
||||||
# return float(measurements[str(uuid)])
|
# return float(measurements[str(uuid)])
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ class TestActionScheduling(base.DbTestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestActionScheduling, self).setUp()
|
super(TestActionScheduling, self).setUp()
|
||||||
|
self.goal = db_utils.create_test_goal(name="dummy")
|
||||||
self.strategy = db_utils.create_test_strategy(name="dummy")
|
self.strategy = db_utils.create_test_strategy(name="dummy")
|
||||||
self.audit = db_utils.create_test_audit(
|
self.audit = db_utils.create_test_audit(
|
||||||
uuid=utils.generate_uuid(), strategy_id=self.strategy.id)
|
uuid=utils.generate_uuid(), strategy_id=self.strategy.id)
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ class TestActionScheduling(base.DbTestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestActionScheduling, self).setUp()
|
super(TestActionScheduling, self).setUp()
|
||||||
|
self.goal = db_utils.create_test_goal(name="dummy")
|
||||||
self.strategy = db_utils.create_test_strategy(name="dummy")
|
self.strategy = db_utils.create_test_strategy(name="dummy")
|
||||||
self.audit = db_utils.create_test_audit(
|
self.audit = db_utils.create_test_audit(
|
||||||
uuid=utils.generate_uuid(), strategy_id=self.strategy.id)
|
uuid=utils.generate_uuid(), strategy_id=self.strategy.id)
|
||||||
|
|||||||
@@ -42,10 +42,8 @@ class TestDefaultScope(base.TestCase):
|
|||||||
for i in range(2)]
|
for i in range(2)]
|
||||||
model = default.DefaultScope(audit_scope,
|
model = default.DefaultScope(audit_scope,
|
||||||
osc=mock.Mock()).get_scoped_model(cluster)
|
osc=mock.Mock()).get_scoped_model(cluster)
|
||||||
|
|
||||||
expected_edges = [('INSTANCE_2', 'Node_1')]
|
expected_edges = [('INSTANCE_2', 'Node_1')]
|
||||||
edges = [(src.uuid, dst.uuid) for src, dst in model.edges()]
|
self.assertEqual(sorted(expected_edges), sorted(model.edges()))
|
||||||
self.assertEqual(sorted(expected_edges), sorted(edges))
|
|
||||||
|
|
||||||
@mock.patch.object(nova_helper.NovaHelper, 'get_availability_zone_list')
|
@mock.patch.object(nova_helper.NovaHelper, 'get_availability_zone_list')
|
||||||
def test_get_scoped_model_without_scope(self, mock_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_6', 'Node_3'),
|
||||||
('INSTANCE_7', 'Node_4'),
|
('INSTANCE_7', 'Node_4'),
|
||||||
]
|
]
|
||||||
edges = [(src.uuid, dst.uuid) for src, dst in model.edges()]
|
self.assertEqual(sorted(expected_edges), sorted(model.edges()))
|
||||||
self.assertEqual(sorted(expected_edges), sorted(edges))
|
|
||||||
|
|
||||||
@mock.patch.object(nova_helper.NovaHelper, 'get_aggregate_detail')
|
@mock.patch.object(nova_helper.NovaHelper, 'get_aggregate_detail')
|
||||||
@mock.patch.object(nova_helper.NovaHelper, 'get_aggregate_list')
|
@mock.patch.object(nova_helper.NovaHelper, 'get_aggregate_list')
|
||||||
@@ -199,8 +196,7 @@ class TestDefaultScope(base.TestCase):
|
|||||||
('INSTANCE_1', 'Node_0'),
|
('INSTANCE_1', 'Node_0'),
|
||||||
('INSTANCE_6', 'Node_3'),
|
('INSTANCE_6', 'Node_3'),
|
||||||
('INSTANCE_7', 'Node_4')]
|
('INSTANCE_7', 'Node_4')]
|
||||||
edges = [(src.uuid, dst.uuid) for src, dst in model.edges()]
|
self.assertEqual(sorted(expected_edges), sorted(model.edges()))
|
||||||
self.assertEqual(sorted(expected_edges), sorted(edges))
|
|
||||||
|
|
||||||
def test_remove_instances_from_model(self):
|
def test_remove_instances_from_model(self):
|
||||||
model = self.fake_cluster.generate_scenario_1()
|
model = self.fake_cluster.generate_scenario_1()
|
||||||
@@ -213,5 +209,4 @@ class TestDefaultScope(base.TestCase):
|
|||||||
('INSTANCE_5', 'Node_2'),
|
('INSTANCE_5', 'Node_2'),
|
||||||
('INSTANCE_6', 'Node_3'),
|
('INSTANCE_6', 'Node_3'),
|
||||||
('INSTANCE_7', 'Node_4')]
|
('INSTANCE_7', 'Node_4')]
|
||||||
edges = [(src.uuid, dst.uuid) for src, dst in model.edges()]
|
self.assertEqual(sorted(expected_edges), sorted(model.edges()))
|
||||||
self.assertEqual(sorted(expected_edges), sorted(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())
|
self.strategy = utils.create_test_strategy(mock.Mock())
|
||||||
|
|
||||||
def test_send_invalid_audit(self):
|
def test_send_invalid_audit(self):
|
||||||
audit = utils.get_test_audit(mock.Mock(), state='DOESNOTMATTER',
|
audit = utils.get_test_audit(
|
||||||
goal_id=1)
|
mock.Mock(), interval=None, state='DOESNOTMATTER', goal_id=1)
|
||||||
|
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
exception.InvalidAudit,
|
exception.InvalidAudit,
|
||||||
@@ -53,7 +53,7 @@ class TestAuditNotification(base.DbTestCase):
|
|||||||
|
|
||||||
def test_send_audit_update_with_strategy(self):
|
def test_send_audit_update_with_strategy(self):
|
||||||
audit = utils.create_test_audit(
|
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_id=self.goal.id, strategy_id=self.strategy.id,
|
||||||
goal=self.goal, strategy=self.strategy)
|
goal=self.goal, strategy=self.strategy)
|
||||||
notifications.audit.send_update(
|
notifications.audit.send_update(
|
||||||
@@ -71,7 +71,8 @@ class TestAuditNotification(base.DbTestCase):
|
|||||||
"watcher_object.namespace": "watcher",
|
"watcher_object.namespace": "watcher",
|
||||||
"watcher_object.version": "1.0",
|
"watcher_object.version": "1.0",
|
||||||
"watcher_object.data": {
|
"watcher_object.data": {
|
||||||
"interval": 3600,
|
"interval": None,
|
||||||
|
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||||
"strategy": {
|
"strategy": {
|
||||||
"watcher_object.namespace": "watcher",
|
"watcher_object.namespace": "watcher",
|
||||||
"watcher_object.version": "1.0",
|
"watcher_object.version": "1.0",
|
||||||
@@ -88,6 +89,7 @@ class TestAuditNotification(base.DbTestCase):
|
|||||||
},
|
},
|
||||||
"parameters": {},
|
"parameters": {},
|
||||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||||
|
"goal_uuid": "f7ad87ae-4298-91cf-93a0-f35a852e3652",
|
||||||
"goal": {
|
"goal": {
|
||||||
"watcher_object.namespace": "watcher",
|
"watcher_object.namespace": "watcher",
|
||||||
"watcher_object.version": "1.0",
|
"watcher_object.version": "1.0",
|
||||||
@@ -125,7 +127,7 @@ class TestAuditNotification(base.DbTestCase):
|
|||||||
|
|
||||||
def test_send_audit_update_without_strategy(self):
|
def test_send_audit_update_without_strategy(self):
|
||||||
audit = utils.get_test_audit(
|
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)
|
goal_id=self.goal.id, goal=self.goal)
|
||||||
notifications.audit.send_update(
|
notifications.audit.send_update(
|
||||||
mock.MagicMock(), audit, host='node0',
|
mock.MagicMock(), audit, host='node0',
|
||||||
@@ -141,9 +143,10 @@ class TestAuditNotification(base.DbTestCase):
|
|||||||
"watcher_object.namespace": "watcher",
|
"watcher_object.namespace": "watcher",
|
||||||
"watcher_object.version": "1.0",
|
"watcher_object.version": "1.0",
|
||||||
"watcher_object.data": {
|
"watcher_object.data": {
|
||||||
"interval": 3600,
|
"interval": None,
|
||||||
"parameters": {},
|
"parameters": {},
|
||||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||||
|
"goal_uuid": "f7ad87ae-4298-91cf-93a0-f35a852e3652",
|
||||||
"goal": {
|
"goal": {
|
||||||
"watcher_object.namespace": "watcher",
|
"watcher_object.namespace": "watcher",
|
||||||
"watcher_object.version": "1.0",
|
"watcher_object.version": "1.0",
|
||||||
@@ -158,6 +161,7 @@ class TestAuditNotification(base.DbTestCase):
|
|||||||
},
|
},
|
||||||
"watcher_object.name": "GoalPayload"
|
"watcher_object.name": "GoalPayload"
|
||||||
},
|
},
|
||||||
|
"strategy_uuid": None,
|
||||||
"strategy": None,
|
"strategy": None,
|
||||||
"deleted_at": None,
|
"deleted_at": None,
|
||||||
"scope": [],
|
"scope": [],
|
||||||
@@ -182,7 +186,7 @@ class TestAuditNotification(base.DbTestCase):
|
|||||||
|
|
||||||
def test_send_audit_create(self):
|
def test_send_audit_create(self):
|
||||||
audit = utils.get_test_audit(
|
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_id=self.goal.id, strategy_id=self.strategy.id,
|
||||||
goal=self.goal.as_dict(), strategy=self.strategy.as_dict())
|
goal=self.goal.as_dict(), strategy=self.strategy.as_dict())
|
||||||
notifications.audit.send_create(
|
notifications.audit.send_create(
|
||||||
@@ -198,7 +202,8 @@ class TestAuditNotification(base.DbTestCase):
|
|||||||
"watcher_object.namespace": "watcher",
|
"watcher_object.namespace": "watcher",
|
||||||
"watcher_object.version": "1.0",
|
"watcher_object.version": "1.0",
|
||||||
"watcher_object.data": {
|
"watcher_object.data": {
|
||||||
"interval": 3600,
|
"interval": None,
|
||||||
|
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||||
"strategy": {
|
"strategy": {
|
||||||
"watcher_object.namespace": "watcher",
|
"watcher_object.namespace": "watcher",
|
||||||
"watcher_object.version": "1.0",
|
"watcher_object.version": "1.0",
|
||||||
@@ -215,6 +220,7 @@ class TestAuditNotification(base.DbTestCase):
|
|||||||
},
|
},
|
||||||
"parameters": {},
|
"parameters": {},
|
||||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||||
|
"goal_uuid": "f7ad87ae-4298-91cf-93a0-f35a852e3652",
|
||||||
"goal": {
|
"goal": {
|
||||||
"watcher_object.namespace": "watcher",
|
"watcher_object.namespace": "watcher",
|
||||||
"watcher_object.version": "1.0",
|
"watcher_object.version": "1.0",
|
||||||
@@ -243,7 +249,7 @@ class TestAuditNotification(base.DbTestCase):
|
|||||||
|
|
||||||
def test_send_audit_delete(self):
|
def test_send_audit_delete(self):
|
||||||
audit = utils.create_test_audit(
|
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)
|
goal_id=self.goal.id, strategy_id=self.strategy.id)
|
||||||
notifications.audit.send_delete(
|
notifications.audit.send_delete(
|
||||||
mock.MagicMock(), audit, host='node0')
|
mock.MagicMock(), audit, host='node0')
|
||||||
@@ -259,7 +265,8 @@ class TestAuditNotification(base.DbTestCase):
|
|||||||
"watcher_object.namespace": "watcher",
|
"watcher_object.namespace": "watcher",
|
||||||
"watcher_object.version": "1.0",
|
"watcher_object.version": "1.0",
|
||||||
"watcher_object.data": {
|
"watcher_object.data": {
|
||||||
"interval": 3600,
|
"interval": None,
|
||||||
|
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||||
"strategy": {
|
"strategy": {
|
||||||
"watcher_object.namespace": "watcher",
|
"watcher_object.namespace": "watcher",
|
||||||
"watcher_object.version": "1.0",
|
"watcher_object.version": "1.0",
|
||||||
@@ -276,6 +283,7 @@ class TestAuditNotification(base.DbTestCase):
|
|||||||
},
|
},
|
||||||
"parameters": {},
|
"parameters": {},
|
||||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||||
|
"goal_uuid": "f7ad87ae-4298-91cf-93a0-f35a852e3652",
|
||||||
"goal": {
|
"goal": {
|
||||||
"watcher_object.namespace": "watcher",
|
"watcher_object.namespace": "watcher",
|
||||||
"watcher_object.version": "1.0",
|
"watcher_object.version": "1.0",
|
||||||
@@ -304,7 +312,7 @@ class TestAuditNotification(base.DbTestCase):
|
|||||||
|
|
||||||
def test_send_audit_action(self):
|
def test_send_audit_action(self):
|
||||||
audit = utils.create_test_audit(
|
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_id=self.goal.id, strategy_id=self.strategy.id,
|
||||||
goal=self.goal, strategy=self.strategy)
|
goal=self.goal, strategy=self.strategy)
|
||||||
notifications.audit.send_action_notification(
|
notifications.audit.send_action_notification(
|
||||||
@@ -326,6 +334,7 @@ class TestAuditNotification(base.DbTestCase):
|
|||||||
"created_at": "2016-10-18T09:52:05Z",
|
"created_at": "2016-10-18T09:52:05Z",
|
||||||
"deleted_at": None,
|
"deleted_at": None,
|
||||||
"fault": None,
|
"fault": None,
|
||||||
|
"goal_uuid": "f7ad87ae-4298-91cf-93a0-f35a852e3652",
|
||||||
"goal": {
|
"goal": {
|
||||||
"watcher_object.data": {
|
"watcher_object.data": {
|
||||||
"created_at": "2016-10-18T09:52:05Z",
|
"created_at": "2016-10-18T09:52:05Z",
|
||||||
@@ -340,10 +349,12 @@ class TestAuditNotification(base.DbTestCase):
|
|||||||
"watcher_object.namespace": "watcher",
|
"watcher_object.namespace": "watcher",
|
||||||
"watcher_object.version": "1.0"
|
"watcher_object.version": "1.0"
|
||||||
},
|
},
|
||||||
"interval": 3600,
|
"interval": None,
|
||||||
"parameters": {},
|
"parameters": {},
|
||||||
"scope": [],
|
"scope": [],
|
||||||
"state": "ONGOING",
|
"state": "ONGOING",
|
||||||
|
"strategy_uuid": (
|
||||||
|
"cb3d0b58-4415-4d90-b75b-1e96878730e3"),
|
||||||
"strategy": {
|
"strategy": {
|
||||||
"watcher_object.data": {
|
"watcher_object.data": {
|
||||||
"created_at": "2016-10-18T09:52:05Z",
|
"created_at": "2016-10-18T09:52:05Z",
|
||||||
@@ -371,7 +382,7 @@ class TestAuditNotification(base.DbTestCase):
|
|||||||
|
|
||||||
def test_send_audit_action_with_error(self):
|
def test_send_audit_action_with_error(self):
|
||||||
audit = utils.create_test_audit(
|
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_id=self.goal.id, strategy_id=self.strategy.id,
|
||||||
goal=self.goal, strategy=self.strategy)
|
goal=self.goal, strategy=self.strategy)
|
||||||
|
|
||||||
@@ -407,6 +418,7 @@ class TestAuditNotification(base.DbTestCase):
|
|||||||
"watcher_object.namespace": "watcher",
|
"watcher_object.namespace": "watcher",
|
||||||
"watcher_object.version": "1.0"
|
"watcher_object.version": "1.0"
|
||||||
},
|
},
|
||||||
|
"goal_uuid": "f7ad87ae-4298-91cf-93a0-f35a852e3652",
|
||||||
"goal": {
|
"goal": {
|
||||||
"watcher_object.data": {
|
"watcher_object.data": {
|
||||||
"created_at": "2016-10-18T09:52:05Z",
|
"created_at": "2016-10-18T09:52:05Z",
|
||||||
@@ -421,10 +433,12 @@ class TestAuditNotification(base.DbTestCase):
|
|||||||
"watcher_object.namespace": "watcher",
|
"watcher_object.namespace": "watcher",
|
||||||
"watcher_object.version": "1.0"
|
"watcher_object.version": "1.0"
|
||||||
},
|
},
|
||||||
"interval": 3600,
|
"interval": None,
|
||||||
"parameters": {},
|
"parameters": {},
|
||||||
"scope": [],
|
"scope": [],
|
||||||
"state": "ONGOING",
|
"state": "ONGOING",
|
||||||
|
"strategy_uuid": (
|
||||||
|
"cb3d0b58-4415-4d90-b75b-1e96878730e3"),
|
||||||
"strategy": {
|
"strategy": {
|
||||||
"watcher_object.data": {
|
"watcher_object.data": {
|
||||||
"created_at": "2016-10-18T09:52:05Z",
|
"created_at": "2016-10-18T09:52:05Z",
|
||||||
|
|||||||
@@ -250,10 +250,11 @@ class TestNotificationBase(testbase.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
expected_notification_fingerprints = {
|
expected_notification_fingerprints = {
|
||||||
'EventType': '1.2-633c2d32fa849d2a6f8bda3b0db88332',
|
'EventType': '1.3-4258a2c86eca79fd34a7dffe1278eab9',
|
||||||
'ExceptionNotification': '1.0-9b69de0724fda8310d05e18418178866',
|
'ExceptionNotification': '1.0-9b69de0724fda8310d05e18418178866',
|
||||||
'ExceptionPayload': '1.0-4516ae282a55fe2fd5c754967ee6248b',
|
'ExceptionPayload': '1.0-4516ae282a55fe2fd5c754967ee6248b',
|
||||||
'NotificationPublisher': '1.0-bbbc1402fb0e443a3eb227cc52b61545',
|
'NotificationPublisher': '1.0-bbbc1402fb0e443a3eb227cc52b61545',
|
||||||
|
'TerseAuditPayload': '1.0-aaf31166b8698f08d12cae98c380b8e0',
|
||||||
'AuditPayload': '1.0-30c85c834648c8ca11f54fc5e084d86b',
|
'AuditPayload': '1.0-30c85c834648c8ca11f54fc5e084d86b',
|
||||||
'AuditStateUpdatePayload': '1.0-1a1b606bf14a2c468800c2b010801ce5',
|
'AuditStateUpdatePayload': '1.0-1a1b606bf14a2c468800c2b010801ce5',
|
||||||
'AuditUpdateNotification': '1.0-9b69de0724fda8310d05e18418178866',
|
'AuditUpdateNotification': '1.0-9b69de0724fda8310d05e18418178866',
|
||||||
@@ -266,6 +267,16 @@ expected_notification_fingerprints = {
|
|||||||
'AuditActionPayload': '1.0-09f5d005f94ba9e5f6b9200170332c52',
|
'AuditActionPayload': '1.0-09f5d005f94ba9e5f6b9200170332c52',
|
||||||
'GoalPayload': '1.0-fa1fecb8b01dd047eef808ded4d50d1a',
|
'GoalPayload': '1.0-fa1fecb8b01dd047eef808ded4d50d1a',
|
||||||
'StrategyPayload': '1.0-94f01c137b083ac236ae82573c1fcfc1',
|
'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.common import exception
|
||||||
from watcher.db.sqlalchemy import api as db_api
|
from watcher.db.sqlalchemy import api as db_api
|
||||||
|
from watcher import notifications
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
from watcher.tests.db import base
|
from watcher.tests.db import base
|
||||||
from watcher.tests.db import utils
|
from watcher.tests.db import utils
|
||||||
@@ -34,16 +35,19 @@ class TestActionPlanObject(base.DbTestCase):
|
|||||||
('non_eager', dict(
|
('non_eager', dict(
|
||||||
eager=False,
|
eager=False,
|
||||||
fake_action_plan=utils.get_test_action_plan(
|
fake_action_plan=utils.get_test_action_plan(
|
||||||
|
created_at=datetime.datetime.utcnow(),
|
||||||
audit_id=audit_id,
|
audit_id=audit_id,
|
||||||
strategy_id=strategy_id))),
|
strategy_id=strategy_id))),
|
||||||
('eager_with_non_eager_load', dict(
|
('eager_with_non_eager_load', dict(
|
||||||
eager=True,
|
eager=True,
|
||||||
fake_action_plan=utils.get_test_action_plan(
|
fake_action_plan=utils.get_test_action_plan(
|
||||||
|
created_at=datetime.datetime.utcnow(),
|
||||||
audit_id=audit_id,
|
audit_id=audit_id,
|
||||||
strategy_id=strategy_id))),
|
strategy_id=strategy_id))),
|
||||||
('eager_with_eager_load', dict(
|
('eager_with_eager_load', dict(
|
||||||
eager=True,
|
eager=True,
|
||||||
fake_action_plan=utils.get_test_action_plan(
|
fake_action_plan=utils.get_test_action_plan(
|
||||||
|
created_at=datetime.datetime.utcnow(),
|
||||||
strategy_id=strategy_id,
|
strategy_id=strategy_id,
|
||||||
strategy=utils.get_test_strategy(id=strategy_id),
|
strategy=utils.get_test_strategy(id=strategy_id),
|
||||||
audit_id=audit_id,
|
audit_id=audit_id,
|
||||||
@@ -52,6 +56,13 @@ class TestActionPlanObject(base.DbTestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestActionPlanObject, self).setUp()
|
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_audit = utils.create_test_audit(id=self.audit_id)
|
||||||
self.fake_strategy = utils.create_test_strategy(
|
self.fake_strategy = utils.create_test_strategy(
|
||||||
id=self.strategy_id, name="DUMMY")
|
id=self.strategy_id, name="DUMMY")
|
||||||
@@ -80,6 +91,7 @@ class TestActionPlanObject(base.DbTestCase):
|
|||||||
self.context, action_plan_id, eager=self.eager)
|
self.context, action_plan_id, eager=self.eager)
|
||||||
self.assertEqual(self.context, action_plan._context)
|
self.assertEqual(self.context, action_plan._context)
|
||||||
self.eager_load_action_plan_assert(action_plan)
|
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')
|
@mock.patch.object(db_api.Connection, 'get_action_plan_by_uuid')
|
||||||
def test_get_by_uuid(self, mock_get_action_plan):
|
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.context, uuid, eager=self.eager)
|
||||||
self.assertEqual(self.context, action_plan._context)
|
self.assertEqual(self.context, action_plan._context)
|
||||||
self.eager_load_action_plan_assert(action_plan)
|
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):
|
def test_get_bad_id_and_uuid(self):
|
||||||
self.assertRaises(exception.InvalidIdentity,
|
self.assertRaises(exception.InvalidIdentity,
|
||||||
@@ -107,14 +120,26 @@ class TestActionPlanObject(base.DbTestCase):
|
|||||||
self.assertEqual(self.context, action_plans[0]._context)
|
self.assertEqual(self.context, action_plans[0]._context)
|
||||||
for action_plan in action_plans:
|
for action_plan in action_plans:
|
||||||
self.eager_load_action_plan_assert(action_plan)
|
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, 'update_action_plan')
|
||||||
@mock.patch.object(db_api.Connection, 'get_action_plan_by_uuid')
|
@mock.patch.object(db_api.Connection, 'get_action_plan_by_uuid')
|
||||||
def test_save(self, mock_get_action_plan, mock_update_action_plan):
|
def test_save(self, mock_get_action_plan, mock_update_action_plan):
|
||||||
mock_get_action_plan.return_value = self.fake_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 = 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
|
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']
|
uuid = self.fake_action_plan['uuid']
|
||||||
action_plan = objects.ActionPlan.get_by_uuid(
|
action_plan = objects.ActionPlan.get_by_uuid(
|
||||||
self.context, uuid, eager=self.eager)
|
self.context, uuid, eager=self.eager)
|
||||||
@@ -127,6 +152,14 @@ class TestActionPlanObject(base.DbTestCase):
|
|||||||
uuid, {'state': objects.action_plan.State.SUCCEEDED})
|
uuid, {'state': objects.action_plan.State.SUCCEEDED})
|
||||||
self.assertEqual(self.context, action_plan._context)
|
self.assertEqual(self.context, action_plan._context)
|
||||||
self.eager_load_action_plan_assert(action_plan)
|
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')
|
@mock.patch.object(db_api.Connection, 'get_action_plan_by_uuid')
|
||||||
def test_refresh(self, mock_get_action_plan):
|
def test_refresh(self, mock_get_action_plan):
|
||||||
@@ -150,6 +183,13 @@ class TestCreateDeleteActionPlanObject(base.DbTestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestCreateDeleteActionPlanObject, self).setUp()
|
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_strategy = utils.create_test_strategy(name="DUMMY")
|
||||||
self.fake_audit = utils.create_test_audit()
|
self.fake_audit = utils.create_test_audit()
|
||||||
self.fake_action_plan = utils.get_test_action_plan(
|
self.fake_action_plan = utils.get_test_action_plan(
|
||||||
@@ -202,7 +242,8 @@ class TestCreateDeleteActionPlanObject(base.DbTestCase):
|
|||||||
del expected_action_plan['strategy']
|
del expected_action_plan['strategy']
|
||||||
|
|
||||||
m_get_efficacy_indicator_list.return_value = [efficacy_indicator]
|
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()
|
action_plan.soft_delete()
|
||||||
|
|
||||||
m_get_action_plan.assert_called_once_with(
|
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):
|
def test_send_create_notification(self, m_create_audit):
|
||||||
audit = objutils.get_test_audit(
|
audit = objutils.get_test_audit(
|
||||||
self.context,
|
self.context,
|
||||||
|
id=1,
|
||||||
goal_id=self.fake_goal.id,
|
goal_id=self.fake_goal.id,
|
||||||
strategy_id=self.fake_strategy.id,
|
strategy_id=self.fake_strategy.id,
|
||||||
goal=self.fake_goal.as_dict(),
|
goal=self.fake_goal.as_dict(),
|
||||||
|
|||||||
@@ -30,21 +30,27 @@ def _load_related_objects(context, cls, db_data):
|
|||||||
return obj_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):
|
def get_test_audit_template(context, **kw):
|
||||||
"""Return a AuditTemplate object with appropriate attributes.
|
"""Return a AuditTemplate object with appropriate attributes.
|
||||||
|
|
||||||
NOTE: The object leaves the attributes marked as changed, such
|
NOTE: The object leaves the attributes marked as changed, such
|
||||||
that a create() could be used to commit it to the DB.
|
that a create() could be used to commit it to the DB.
|
||||||
"""
|
"""
|
||||||
db_audit_template = db_utils.get_test_audit_template(**kw)
|
obj_cls = objects.AuditTemplate
|
||||||
# Let DB generate ID if it isn't specified explicitly
|
db_data = db_utils.get_test_audit_template(**kw)
|
||||||
if 'id' not in kw:
|
obj_data = _load_related_objects(context, obj_cls, db_data)
|
||||||
del db_audit_template['id']
|
|
||||||
audit_template = objects.AuditTemplate(context)
|
|
||||||
for key in db_audit_template:
|
|
||||||
setattr(audit_template, key, db_audit_template[key])
|
|
||||||
|
|
||||||
return audit_template
|
return _load_test_obj(context, obj_cls, obj_data, **kw)
|
||||||
|
|
||||||
|
|
||||||
def create_test_audit_template(context, **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
|
NOTE: The object leaves the attributes marked as changed, such
|
||||||
that a create() could be used to commit it to the DB.
|
that a create() could be used to commit it to the DB.
|
||||||
"""
|
"""
|
||||||
db_audit = db_utils.get_test_audit(**kw)
|
obj_cls = objects.Audit
|
||||||
obj_data = _load_related_objects(context, objects.Audit, db_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
|
return _load_test_obj(context, obj_cls, obj_data, **kw)
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def create_test_audit(context, **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
|
NOTE: The object leaves the attributes marked as changed, such
|
||||||
that a create() could be used to commit it to the DB.
|
that a create() could be used to commit it to the DB.
|
||||||
"""
|
"""
|
||||||
db_action_plan = db_utils.get_test_action_plan(**kw)
|
obj_cls = objects.ActionPlan
|
||||||
# Let DB generate ID if it isn't specified explicitly
|
db_data = db_utils.get_test_action_plan(**kw)
|
||||||
if 'id' not in kw:
|
obj_data = _load_related_objects(context, obj_cls, db_data)
|
||||||
del db_action_plan['id']
|
|
||||||
action_plan = objects.ActionPlan(context)
|
return _load_test_obj(context, obj_cls, obj_data, **kw)
|
||||||
for key in db_action_plan:
|
|
||||||
setattr(action_plan, key, db_action_plan[key])
|
|
||||||
return action_plan
|
|
||||||
|
|
||||||
|
|
||||||
def create_test_action_plan(context, **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
|
NOTE: The object leaves the attributes marked as changed, such
|
||||||
that a create() could be used to commit it to the DB.
|
that a create() could be used to commit it to the DB.
|
||||||
"""
|
"""
|
||||||
db_action = db_utils.get_test_action(**kw)
|
obj_cls = objects.Action
|
||||||
# Let DB generate ID if it isn't specified explicitly
|
db_data = db_utils.get_test_action(**kw)
|
||||||
if 'id' not in kw:
|
obj_data = _load_related_objects(context, obj_cls, db_data)
|
||||||
del db_action['id']
|
|
||||||
action = objects.Action(context)
|
return _load_test_obj(context, obj_cls, obj_data, **kw)
|
||||||
for key in db_action:
|
|
||||||
setattr(action, key, db_action[key])
|
|
||||||
return action
|
|
||||||
|
|
||||||
|
|
||||||
def create_test_action(context, **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
|
NOTE: The object leaves the attributes marked as changed, such
|
||||||
that a create() could be used to commit it to the DB.
|
that a create() could be used to commit it to the DB.
|
||||||
"""
|
"""
|
||||||
db_goal = db_utils.get_test_goal(**kw)
|
obj_cls = objects.Goal
|
||||||
# Let DB generate ID if it isn't specified explicitly
|
db_data = db_utils.get_test_goal(**kw)
|
||||||
if 'id' not in kw:
|
obj_data = _load_related_objects(context, obj_cls, db_data)
|
||||||
del db_goal['id']
|
|
||||||
goal = objects.Goal(context)
|
return _load_test_obj(context, obj_cls, obj_data, **kw)
|
||||||
for key in db_goal:
|
|
||||||
setattr(goal, key, db_goal[key])
|
|
||||||
return goal
|
|
||||||
|
|
||||||
|
|
||||||
def create_test_goal(context, **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
|
NOTE: The object leaves the attributes marked as changed, such
|
||||||
that a create() could be used to commit it to the DB.
|
that a create() could be used to commit it to the DB.
|
||||||
"""
|
"""
|
||||||
db_scoring_engine = db_utils.get_test_scoring_engine(**kw)
|
obj_cls = objects.ScoringEngine
|
||||||
scoring_engine = objects.ScoringEngine(context)
|
db_data = db_utils.get_test_scoring_engine(**kw)
|
||||||
for key in db_scoring_engine:
|
obj_data = _load_related_objects(context, obj_cls, db_data)
|
||||||
setattr(scoring_engine, key, db_scoring_engine[key])
|
|
||||||
return scoring_engine
|
return _load_test_obj(context, obj_cls, obj_data, **kw)
|
||||||
|
|
||||||
|
|
||||||
def create_test_scoring_engine(context, **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
|
NOTE: The object leaves the attributes marked as changed, such
|
||||||
that a create() could be used to commit it to the DB.
|
that a create() could be used to commit it to the DB.
|
||||||
"""
|
"""
|
||||||
db_service = db_utils.get_test_service(**kw)
|
obj_cls = objects.Service
|
||||||
service = objects.Service(context)
|
db_data = db_utils.get_test_service(**kw)
|
||||||
for key in db_service:
|
obj_data = _load_related_objects(context, obj_cls, db_data)
|
||||||
if key == 'last_seen_up':
|
|
||||||
db_service[key] = None
|
return _load_test_obj(context, obj_cls, obj_data, **kw)
|
||||||
setattr(service, key, db_service[key])
|
|
||||||
return service
|
|
||||||
|
|
||||||
|
|
||||||
def create_test_service(context, **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
|
NOTE: The object leaves the attributes marked as changed, such
|
||||||
that a create() could be used to commit it to the DB.
|
that a create() could be used to commit it to the DB.
|
||||||
"""
|
"""
|
||||||
db_strategy = db_utils.get_test_strategy(**kw)
|
obj_cls = objects.Strategy
|
||||||
# Let DB generate ID if it isn't specified explicitly
|
db_data = db_utils.get_test_strategy(**kw)
|
||||||
if 'id' not in kw:
|
obj_data = _load_related_objects(context, obj_cls, db_data)
|
||||||
del db_strategy['id']
|
|
||||||
strategy = objects.Strategy(context)
|
|
||||||
for key in db_strategy:
|
|
||||||
setattr(strategy, key, db_strategy[key])
|
|
||||||
|
|
||||||
# ObjectField checks for the object type, so if we want to simulate a
|
return _load_test_obj(context, obj_cls, obj_data, **kw)
|
||||||
# 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
|
|
||||||
|
|
||||||
|
|
||||||
def create_test_strategy(context, **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
|
NOTE: The object leaves the attributes marked as changed, such
|
||||||
that a create() could be used to commit it to the DB.
|
that a create() could be used to commit it to the DB.
|
||||||
"""
|
"""
|
||||||
db_efficacy_indicator = db_utils.get_test_efficacy_indicator(**kw)
|
obj_cls = objects.EfficacyIndicator
|
||||||
# Let DB generate ID if it isn't specified explicitly
|
db_data = db_utils.get_test_efficacy_indicator(**kw)
|
||||||
if 'id' not in kw:
|
obj_data = _load_related_objects(context, obj_cls, db_data)
|
||||||
del db_efficacy_indicator['id']
|
|
||||||
efficacy_indicator = objects.EfficacyIndicator(context)
|
return _load_test_obj(context, obj_cls, obj_data, **kw)
|
||||||
for key in db_efficacy_indicator:
|
|
||||||
setattr(efficacy_indicator, key, db_efficacy_indicator[key])
|
|
||||||
return efficacy_indicator
|
|
||||||
|
|
||||||
|
|
||||||
def create_test_efficacy_indicator(context, **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)
|
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 ### #
|
# ### GOALS ### #
|
||||||
|
|
||||||
@base.handle_errors
|
@base.handle_errors
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ class BaseInfraOptimTest(test.BaseTestCase):
|
|||||||
audit_uuid = audit['uuid']
|
audit_uuid = audit['uuid']
|
||||||
|
|
||||||
assert test.call_until_true(
|
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,
|
duration=30,
|
||||||
sleep_for=.5
|
sleep_for=.5
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class TestShowListAction(base.BaseInfraOptimTest):
|
|||||||
_, cls.audit = cls.create_audit(cls.audit_template['uuid'])
|
_, cls.audit = cls.create_audit(cls.audit_template['uuid'])
|
||||||
|
|
||||||
assert test.call_until_true(
|
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,
|
duration=30,
|
||||||
sleep_for=.5
|
sleep_for=.5
|
||||||
)
|
)
|
||||||
@@ -45,19 +45,23 @@ class TestShowListAction(base.BaseInfraOptimTest):
|
|||||||
|
|
||||||
@test.attr(type='smoke')
|
@test.attr(type='smoke')
|
||||||
def test_show_one_action(self):
|
def test_show_one_action(self):
|
||||||
_, action_uuid = self.client.list_actions(
|
_, body = self.client.list_actions(
|
||||||
action_plan_uuid=self.action_plan['uuid'])['actions'][0]['uuid']
|
action_plan_uuid=self.action_plan["uuid"])
|
||||||
_, action = self.client.show_action(action_uuid)
|
actions = body['actions']
|
||||||
|
|
||||||
self.assertEqual(action_uuid, action['uuid'])
|
_, action = self.client.show_action(actions[0]["uuid"])
|
||||||
self.assertEqual("nop", action['action_type'])
|
|
||||||
|
self.assertEqual(self.action_plan["uuid"], action['action_plan_uuid'])
|
||||||
self.assertEqual("PENDING", action['state'])
|
self.assertEqual("PENDING", action['state'])
|
||||||
|
|
||||||
@test.attr(type='smoke')
|
@test.attr(type='smoke')
|
||||||
def test_show_action_with_links(self):
|
def test_show_action_with_links(self):
|
||||||
_, action_uuid = self.client.list_actions(
|
_, body = self.client.list_actions(
|
||||||
action_plan_uuid=self.action_plan['uuid'])['actions'][0]['uuid']
|
action_plan_uuid=self.action_plan["uuid"])
|
||||||
_, action = self.client.show_action(action_uuid)
|
actions = body['actions']
|
||||||
|
|
||||||
|
_, action = self.client.show_action(actions[0]["uuid"])
|
||||||
|
|
||||||
self.assertIn('links', action.keys())
|
self.assertIn('links', action.keys())
|
||||||
self.assertEqual(2, len(action['links']))
|
self.assertEqual(2, len(action['links']))
|
||||||
self.assertIn(action['uuid'], action['links'][0]['href'])
|
self.assertIn(action['uuid'], action['links'][0]['href'])
|
||||||
|
|||||||
@@ -29,12 +29,12 @@ class TestCreateDeleteExecuteActionPlan(base.BaseInfraOptimTest):
|
|||||||
|
|
||||||
@test.attr(type='smoke')
|
@test.attr(type='smoke')
|
||||||
def test_create_action_plan(self):
|
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_template = self.create_audit_template(goal['uuid'])
|
||||||
_, audit = self.create_audit(audit_template['uuid'])
|
_, audit = self.create_audit(audit_template['uuid'])
|
||||||
|
|
||||||
self.assertTrue(test.call_until_true(
|
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,
|
duration=30,
|
||||||
sleep_for=.5
|
sleep_for=.5
|
||||||
))
|
))
|
||||||
@@ -49,12 +49,12 @@ class TestCreateDeleteExecuteActionPlan(base.BaseInfraOptimTest):
|
|||||||
|
|
||||||
@test.attr(type='smoke')
|
@test.attr(type='smoke')
|
||||||
def test_delete_action_plan(self):
|
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_template = self.create_audit_template(goal['uuid'])
|
||||||
_, audit = self.create_audit(audit_template['uuid'])
|
_, audit = self.create_audit(audit_template['uuid'])
|
||||||
|
|
||||||
self.assertTrue(test.call_until_true(
|
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,
|
duration=30,
|
||||||
sleep_for=.5
|
sleep_for=.5
|
||||||
))
|
))
|
||||||
@@ -71,12 +71,12 @@ class TestCreateDeleteExecuteActionPlan(base.BaseInfraOptimTest):
|
|||||||
|
|
||||||
@test.attr(type='smoke')
|
@test.attr(type='smoke')
|
||||||
def test_execute_dummy_action_plan(self):
|
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_template = self.create_audit_template(goal['uuid'])
|
||||||
_, audit = self.create_audit(audit_template['uuid'])
|
_, audit = self.create_audit(audit_template['uuid'])
|
||||||
|
|
||||||
self.assertTrue(test.call_until_true(
|
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,
|
duration=30,
|
||||||
sleep_for=.5
|
sleep_for=.5
|
||||||
))
|
))
|
||||||
@@ -86,11 +86,13 @@ class TestCreateDeleteExecuteActionPlan(base.BaseInfraOptimTest):
|
|||||||
|
|
||||||
_, action_plan = self.client.show_action_plan(action_plan['uuid'])
|
_, 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
|
# Execute the action by changing its state to PENDING
|
||||||
_, updated_ap = self.client.update_action_plan(
|
_, updated_ap = self.client.start_action_plan(action_plan['uuid'])
|
||||||
action_plan['uuid'],
|
|
||||||
patch=[{'path': '/state', 'op': 'replace', 'value': 'PENDING'}]
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertTrue(test.call_until_true(
|
self.assertTrue(test.call_until_true(
|
||||||
func=functools.partial(
|
func=functools.partial(
|
||||||
@@ -110,12 +112,12 @@ class TestShowListActionPlan(base.BaseInfraOptimTest):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def resource_setup(cls):
|
def resource_setup(cls):
|
||||||
super(TestShowListActionPlan, cls).resource_setup()
|
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_template = cls.create_audit_template(cls.goal['uuid'])
|
||||||
_, cls.audit = cls.create_audit(cls.audit_template['uuid'])
|
_, cls.audit = cls.create_audit(cls.audit_template['uuid'])
|
||||||
|
|
||||||
assert test.call_until_true(
|
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,
|
duration=30,
|
||||||
sleep_for=.5
|
sleep_for=.5
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class TestCreateUpdateDeleteAudit(base.BaseInfraOptimTest):
|
|||||||
|
|
||||||
@test.attr(type='smoke')
|
@test.attr(type='smoke')
|
||||||
def test_create_audit_oneshot(self):
|
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_template = self.create_audit_template(goal['uuid'])
|
||||||
|
|
||||||
audit_params = dict(
|
audit_params = dict(
|
||||||
@@ -56,7 +56,7 @@ class TestCreateUpdateDeleteAudit(base.BaseInfraOptimTest):
|
|||||||
|
|
||||||
@test.attr(type='smoke')
|
@test.attr(type='smoke')
|
||||||
def test_create_audit_continuous(self):
|
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_template = self.create_audit_template(goal['uuid'])
|
||||||
|
|
||||||
audit_params = dict(
|
audit_params = dict(
|
||||||
@@ -85,7 +85,7 @@ class TestCreateUpdateDeleteAudit(base.BaseInfraOptimTest):
|
|||||||
|
|
||||||
@test.attr(type='smoke')
|
@test.attr(type='smoke')
|
||||||
def test_create_audit_with_invalid_state(self):
|
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_template = self.create_audit_template(goal['uuid'])
|
||||||
|
|
||||||
audit_params = dict(
|
audit_params = dict(
|
||||||
@@ -98,7 +98,7 @@ class TestCreateUpdateDeleteAudit(base.BaseInfraOptimTest):
|
|||||||
|
|
||||||
@test.attr(type='smoke')
|
@test.attr(type='smoke')
|
||||||
def test_create_audit_with_no_state(self):
|
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_template = self.create_audit_template(goal['uuid'])
|
||||||
|
|
||||||
audit_params = dict(
|
audit_params = dict(
|
||||||
@@ -120,7 +120,7 @@ class TestCreateUpdateDeleteAudit(base.BaseInfraOptimTest):
|
|||||||
|
|
||||||
@test.attr(type='smoke')
|
@test.attr(type='smoke')
|
||||||
def test_delete_audit(self):
|
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'])
|
_, audit_template = self.create_audit_template(goal['uuid'])
|
||||||
_, body = self.create_audit(audit_template['uuid'])
|
_, body = self.create_audit(audit_template['uuid'])
|
||||||
audit_uuid = body['uuid']
|
audit_uuid = body['uuid']
|
||||||
@@ -158,7 +158,7 @@ class TestShowListAudit(base.BaseInfraOptimTest):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def resource_setup(cls):
|
def resource_setup(cls):
|
||||||
super(TestShowListAudit, cls).resource_setup()
|
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_template = cls.create_audit_template(cls.goal['uuid'])
|
||||||
_, cls.audit = cls.create_audit(cls.audit_template['uuid'])
|
_, cls.audit = cls.create_audit(cls.audit_template['uuid'])
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,11 @@ CONF = config.CONF
|
|||||||
class BaseInfraOptimScenarioTest(manager.ScenarioTest):
|
class BaseInfraOptimScenarioTest(manager.ScenarioTest):
|
||||||
"""Base class for Infrastructure Optimization API tests."""
|
"""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
|
@classmethod
|
||||||
def setup_credentials(cls):
|
def setup_credentials(cls):
|
||||||
cls._check_network_config()
|
cls._check_network_config()
|
||||||
@@ -142,6 +147,11 @@ class BaseInfraOptimScenarioTest(manager.ScenarioTest):
|
|||||||
|
|
||||||
return audit.get('state') == 'SUCCEEDED'
|
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 ### #
|
# ### ACTION PLANS ### #
|
||||||
|
|
||||||
def delete_action_plan(self, action_plan_uuid):
|
def delete_action_plan(self, action_plan_uuid):
|
||||||
|
|||||||
@@ -148,24 +148,31 @@ class TestExecuteBasicStrategy(base.BaseInfraOptimScenarioTest):
|
|||||||
try:
|
try:
|
||||||
self.assertTrue(test.call_until_true(
|
self.assertTrue(test.call_until_true(
|
||||||
func=functools.partial(
|
func=functools.partial(
|
||||||
self.has_audit_succeeded, audit['uuid']),
|
self.has_audit_finished, audit['uuid']),
|
||||||
duration=600,
|
duration=600,
|
||||||
sleep_for=2
|
sleep_for=2
|
||||||
))
|
))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self.fail("The audit has failed!")
|
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(
|
_, action_plans = self.client.list_action_plans(
|
||||||
audit_uuid=audit['uuid'])
|
audit_uuid=audit['uuid'])
|
||||||
action_plan = action_plans['action_plans'][0]
|
action_plan = action_plans['action_plans'][0]
|
||||||
|
|
||||||
_, action_plan = self.client.show_action_plan(action_plan['uuid'])
|
_, 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
|
# Execute the action by changing its state to PENDING
|
||||||
_, updated_ap = self.client.update_action_plan(
|
_, updated_ap = self.client.start_action_plan(action_plan['uuid'])
|
||||||
action_plan['uuid'],
|
|
||||||
patch=[{'path': '/state', 'op': 'replace', 'value': 'PENDING'}]
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertTrue(test.call_until_true(
|
self.assertTrue(test.call_until_true(
|
||||||
func=functools.partial(
|
func=functools.partial(
|
||||||
|
|||||||
@@ -42,21 +42,26 @@ class TestExecuteDummyStrategy(base.BaseInfraOptimScenarioTest):
|
|||||||
_, audit = self.create_audit(audit_template['uuid'])
|
_, audit = self.create_audit(audit_template['uuid'])
|
||||||
|
|
||||||
self.assertTrue(test.call_until_true(
|
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,
|
duration=30,
|
||||||
sleep_for=.5
|
sleep_for=.5
|
||||||
))
|
))
|
||||||
|
|
||||||
|
self.assertTrue(self.has_audit_succeeded(audit['uuid']))
|
||||||
|
|
||||||
_, action_plans = self.client.list_action_plans(
|
_, action_plans = self.client.list_action_plans(
|
||||||
audit_uuid=audit['uuid'])
|
audit_uuid=audit['uuid'])
|
||||||
action_plan = action_plans['action_plans'][0]
|
action_plan = action_plans['action_plans'][0]
|
||||||
|
|
||||||
_, action_plan = self.client.show_action_plan(action_plan['uuid'])
|
_, 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
|
# Execute the action by changing its state to PENDING
|
||||||
_, updated_ap = self.client.update_action_plan(
|
_, updated_ap = self.client.start_action_plan(action_plan['uuid'])
|
||||||
action_plan['uuid'],
|
|
||||||
patch=[{'path': '/state', 'op': 'replace', 'value': 'PENDING'}]
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertTrue(test.call_until_true(
|
self.assertTrue(test.call_until_true(
|
||||||
func=functools.partial(
|
func=functools.partial(
|
||||||
|
|||||||
Reference in New Issue
Block a user