add event for scaleset state updates (#882)

This moves all scaleset state updates through `Scaleset.set_state` and adds a new event EventScalesetStateUpdated.
This commit is contained in:
bmc-msft
2021-05-13 17:23:02 -04:00
committed by GitHub
parent 372c194f7a
commit cb5e786bcd
7 changed files with 146 additions and 32 deletions

View File

@ -41,6 +41,7 @@ Each event will be submitted via HTTP POST to the user provided URL.
* [scaleset_created](#scaleset_created) * [scaleset_created](#scaleset_created)
* [scaleset_deleted](#scaleset_deleted) * [scaleset_deleted](#scaleset_deleted)
* [scaleset_failed](#scaleset_failed) * [scaleset_failed](#scaleset_failed)
* [scaleset_state_updated](#scaleset_state_updated)
* [task_created](#task_created) * [task_created](#task_created)
* [task_failed](#task_failed) * [task_failed](#task_failed)
* [task_heartbeat](#task_heartbeat) * [task_heartbeat](#task_heartbeat)
@ -2203,6 +2204,61 @@ Each event will be submitted via HTTP POST to the user provided URL.
} }
``` ```
### scaleset_state_updated
#### Example
```json
{
"pool_name": "example",
"scaleset_id": "00000000-0000-0000-0000-000000000000",
"state": "init"
}
```
#### Schema
```json
{
"definitions": {
"ScalesetState": {
"description": "An enumeration.",
"enum": [
"init",
"setup",
"resize",
"running",
"shutdown",
"halt",
"creation_failed"
],
"title": "ScalesetState"
}
},
"properties": {
"pool_name": {
"title": "Pool Name",
"type": "string"
},
"scaleset_id": {
"format": "uuid",
"title": "Scaleset Id",
"type": "string"
},
"state": {
"$ref": "#/definitions/ScalesetState"
}
},
"required": [
"scaleset_id",
"pool_name",
"state"
],
"title": "EventScalesetStateUpdated",
"type": "object"
}
```
### task_created ### task_created
#### Example #### Example
@ -4980,6 +5036,29 @@ Each event will be submitted via HTTP POST to the user provided URL.
"title": "EventScalesetFailed", "title": "EventScalesetFailed",
"type": "object" "type": "object"
}, },
"EventScalesetStateUpdated": {
"properties": {
"pool_name": {
"title": "Pool Name",
"type": "string"
},
"scaleset_id": {
"format": "uuid",
"title": "Scaleset Id",
"type": "string"
},
"state": {
"$ref": "#/definitions/ScalesetState"
}
},
"required": [
"scaleset_id",
"pool_name",
"state"
],
"title": "EventScalesetStateUpdated",
"type": "object"
},
"EventTaskCreated": { "EventTaskCreated": {
"properties": { "properties": {
"config": { "config": {
@ -5139,6 +5218,7 @@ Each event will be submitted via HTTP POST to the user provided URL.
"scaleset_created", "scaleset_created",
"scaleset_deleted", "scaleset_deleted",
"scaleset_failed", "scaleset_failed",
"scaleset_state_updated",
"task_created", "task_created",
"task_failed", "task_failed",
"task_state_updated", "task_state_updated",
@ -5374,6 +5454,19 @@ Each event will be submitted via HTTP POST to the user provided URL.
"title": "Report", "title": "Report",
"type": "object" "type": "object"
}, },
"ScalesetState": {
"description": "An enumeration.",
"enum": [
"init",
"setup",
"resize",
"running",
"shutdown",
"halt",
"creation_failed"
],
"title": "ScalesetState"
},
"StatsFormat": { "StatsFormat": {
"description": "An enumeration.", "description": "An enumeration.",
"enum": [ "enum": [
@ -5782,6 +5875,9 @@ Each event will be submitted via HTTP POST to the user provided URL.
{ {
"$ref": "#/definitions/EventScalesetDeleted" "$ref": "#/definitions/EventScalesetDeleted"
}, },
{
"$ref": "#/definitions/EventScalesetStateUpdated"
},
{ {
"$ref": "#/definitions/EventTaskFailed" "$ref": "#/definitions/EventTaskFailed"
}, },

View File

@ -40,8 +40,7 @@ def scale_up(pool: Pool, scalesets: List[Scaleset], nodes_needed: int) -> None:
else: else:
scaleset.size = max_size scaleset.size = max_size
nodes_needed = nodes_needed - (max_size - current_size) nodes_needed = nodes_needed - (max_size - current_size)
scaleset.state = ScalesetState.resize scaleset.set_state(ScalesetState.resize)
scaleset.save()
else: else:
continue continue
@ -90,8 +89,7 @@ def scale_down(scalesets: List[Scaleset], nodes_to_remove: int) -> None:
ScalesetState.shutdown, ScalesetState.shutdown,
ScalesetState.halt, ScalesetState.halt,
]: ]:
scaleset.state = ScalesetState.resize scaleset.set_state(ScalesetState.resize)
scaleset.save()
free_nodes = Node.search_states( free_nodes = Node.search_states(
scaleset_id=scaleset.scaleset_id, scaleset_id=scaleset.scaleset_id,
@ -107,9 +105,8 @@ def scale_down(scalesets: List[Scaleset], nodes_to_remove: int) -> None:
max_nodes_remove = min(len(nodes), nodes_to_remove) max_nodes_remove = min(len(nodes), nodes_to_remove)
# All nodes in scaleset are free. Can shutdown VMSS # All nodes in scaleset are free. Can shutdown VMSS
if max_nodes_remove >= scaleset.size and len(nodes) >= scaleset.size: if max_nodes_remove >= scaleset.size and len(nodes) >= scaleset.size:
scaleset.state = ScalesetState.shutdown scaleset.set_state(ScalesetState.shutdown)
nodes_to_remove = nodes_to_remove - scaleset.size nodes_to_remove = nodes_to_remove - scaleset.size
scaleset.save()
for node in nodes: for node in nodes:
node.set_shutdown() node.set_shutdown()
continue continue
@ -117,8 +114,7 @@ def scale_down(scalesets: List[Scaleset], nodes_to_remove: int) -> None:
# Resize of VMSS needed # Resize of VMSS needed
scaleset.size = scaleset.size - max_nodes_remove scaleset.size = scaleset.size - max_nodes_remove
nodes_to_remove = nodes_to_remove - max_nodes_remove nodes_to_remove = nodes_to_remove - max_nodes_remove
scaleset.state = ScalesetState.resize scaleset.set_state(ScalesetState.resize)
scaleset.save()
def get_vm_count(tasks: List[Task]) -> int: def get_vm_count(tasks: List[Task]) -> int:

View File

@ -222,8 +222,7 @@ class Pool(BASE_POOL, ORMMixin):
return return
for scaleset in scalesets: for scaleset in scalesets:
scaleset.state = ScalesetState.halt scaleset.set_state(ScalesetState.halt)
scaleset.save()
for node in nodes: for node in nodes:
node.set_halt() node.set_halt()

View File

@ -13,6 +13,7 @@ from onefuzztypes.events import (
EventScalesetCreated, EventScalesetCreated,
EventScalesetDeleted, EventScalesetDeleted,
EventScalesetFailed, EventScalesetFailed,
EventScalesetStateUpdated,
) )
from onefuzztypes.models import Error from onefuzztypes.models import Error
from onefuzztypes.models import Scaleset as BASE_SCALESET from onefuzztypes.models import Scaleset as BASE_SCALESET
@ -141,8 +142,7 @@ class Scaleset(BASE_SCALESET, ORMMixin):
return return
self.error = error self.error = error
self.state = ScalesetState.creation_failed self.set_state(ScalesetState.creation_failed)
self.save()
send_event( send_event(
EventScalesetFailed( EventScalesetFailed(
@ -184,11 +184,9 @@ class Scaleset(BASE_SCALESET, ORMMixin):
self.set_failed(error) self.set_failed(error)
return return
else: else:
self.state = ScalesetState.setup self.set_state(ScalesetState.setup)
else: else:
self.state = ScalesetState.setup self.set_state(ScalesetState.setup)
self.save()
def setup(self) -> None: def setup(self) -> None:
from .pools import Pool from .pools import Pool
@ -269,7 +267,7 @@ class Scaleset(BASE_SCALESET, ORMMixin):
self.set_failed(identity_result) self.set_failed(identity_result)
return return
else: else:
self.state = ScalesetState.running self.set_state(ScalesetState.running)
self.save() self.save()
def try_set_identity(self, vmss: Any) -> Optional[Error]: def try_set_identity(self, vmss: Any) -> Optional[Error]:
@ -414,9 +412,7 @@ class Scaleset(BASE_SCALESET, ORMMixin):
node_count = len(Node.search_states(scaleset_id=self.scaleset_id)) node_count = len(Node.search_states(scaleset_id=self.scaleset_id))
if node_count == self.size: if node_count == self.size:
logging.info(SCALESET_LOG_PREFIX + "resize finished: %s", self.scaleset_id) logging.info(SCALESET_LOG_PREFIX + "resize finished: %s", self.scaleset_id)
self.state = ScalesetState.running self.set_state(ScalesetState.running)
self.save()
return
else: else:
logging.info( logging.info(
SCALESET_LOG_PREFIX SCALESET_LOG_PREFIX
@ -426,7 +422,6 @@ class Scaleset(BASE_SCALESET, ORMMixin):
node_count, node_count,
self.size, self.size,
) )
return
def _resize_grow(self) -> None: def _resize_grow(self) -> None:
try: try:
@ -577,10 +572,16 @@ class Scaleset(BASE_SCALESET, ORMMixin):
if self.state in [ScalesetState.halt, ScalesetState.shutdown]: if self.state in [ScalesetState.halt, ScalesetState.shutdown]:
return return
logging.info(
SCALESET_LOG_PREFIX + "scaleset set_shutdown: scaleset_id:%s now:%s",
self.scaleset_id,
now,
)
if now: if now:
self.state = ScalesetState.halt self.set_state(ScalesetState.halt)
else: else:
self.state = ScalesetState.shutdown self.set_state(ScalesetState.shutdown)
self.save() self.save()
@ -736,6 +737,18 @@ class Scaleset(BASE_SCALESET, ORMMixin):
EventScalesetDeleted(scaleset_id=self.scaleset_id, pool_name=self.pool_name) EventScalesetDeleted(scaleset_id=self.scaleset_id, pool_name=self.pool_name)
) )
def set_state(self, state: ScalesetState) -> None:
if self.state == state:
return
self.state = state
self.save()
send_event(
EventScalesetStateUpdated(
scaleset_id=self.scaleset_id, pool_name=self.pool_name, state=self.state
)
)
class ShrinkEntry(BaseModel): class ShrinkEntry(BaseModel):
shrink_id: UUID = Field(default_factory=uuid4) shrink_id: UUID = Field(default_factory=uuid4)

View File

@ -109,13 +109,7 @@ def delete(req: func.HttpRequest) -> func.HttpResponse:
if isinstance(scaleset, Error): if isinstance(scaleset, Error):
return not_ok(scaleset, context="scaleset stop") return not_ok(scaleset, context="scaleset stop")
if request.now: scaleset.set_shutdown(request.now)
scaleset.state = ScalesetState.halt
else:
scaleset.state = ScalesetState.shutdown
scaleset.save()
scaleset.auth = None
return ok(BoolResult(result=True)) return ok(BoolResult(result=True))
@ -139,7 +133,7 @@ def patch(req: func.HttpRequest) -> func.HttpResponse:
if request.size is not None: if request.size is not None:
scaleset.size = request.size scaleset.size = request.size
scaleset.state = ScalesetState.resize scaleset.set_state(ScalesetState.resize)
scaleset.save() scaleset.save()
scaleset.auth = None scaleset.auth = None

View File

@ -12,6 +12,7 @@ from onefuzztypes.enums import (
ContainerType, ContainerType,
ErrorCode, ErrorCode,
NodeState, NodeState,
ScalesetState,
TaskState, TaskState,
TaskType, TaskType,
) )
@ -35,6 +36,7 @@ from onefuzztypes.events import (
EventScalesetCreated, EventScalesetCreated,
EventScalesetDeleted, EventScalesetDeleted,
EventScalesetFailed, EventScalesetFailed,
EventScalesetStateUpdated,
EventTaskCreated, EventTaskCreated,
EventTaskFailed, EventTaskFailed,
EventTaskHeartbeat, EventTaskHeartbeat,
@ -176,6 +178,11 @@ def main() -> None:
), ),
), ),
EventScalesetDeleted(scaleset_id=UUID(int=0), pool_name=PoolName("example")), EventScalesetDeleted(scaleset_id=UUID(int=0), pool_name=PoolName("example")),
EventScalesetStateUpdated(
scaleset_id=UUID(int=0),
pool_name=PoolName("example"),
state=ScalesetState.init,
),
EventJobCreated( EventJobCreated(
job_id=UUID(int=0), job_id=UUID(int=0),
config=JobConfig( config=JobConfig(

View File

@ -10,7 +10,7 @@ from uuid import UUID, uuid4
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from .enums import OS, Architecture, NodeState, TaskState, TaskType from .enums import OS, Architecture, NodeState, ScalesetState, TaskState, TaskType
from .models import ( from .models import (
AutoScaleConfig, AutoScaleConfig,
Error, Error,
@ -150,6 +150,12 @@ class EventNodeDeleted(BaseEvent):
pool_name: PoolName pool_name: PoolName
class EventScalesetStateUpdated(BaseEvent):
scaleset_id: UUID
pool_name: PoolName
state: ScalesetState
class EventNodeStateUpdated(BaseEvent): class EventNodeStateUpdated(BaseEvent):
machine_id: UUID machine_id: UUID
scaleset_id: Optional[UUID] scaleset_id: Optional[UUID]
@ -192,6 +198,7 @@ Event = Union[
EventScalesetFailed, EventScalesetFailed,
EventScalesetCreated, EventScalesetCreated,
EventScalesetDeleted, EventScalesetDeleted,
EventScalesetStateUpdated,
EventTaskFailed, EventTaskFailed,
EventTaskStateUpdated, EventTaskStateUpdated,
EventTaskCreated, EventTaskCreated,
@ -218,6 +225,7 @@ class EventType(Enum):
scaleset_created = "scaleset_created" scaleset_created = "scaleset_created"
scaleset_deleted = "scaleset_deleted" scaleset_deleted = "scaleset_deleted"
scaleset_failed = "scaleset_failed" scaleset_failed = "scaleset_failed"
scaleset_state_updated = "scaleset_state_updated"
task_created = "task_created" task_created = "task_created"
task_failed = "task_failed" task_failed = "task_failed"
task_state_updated = "task_state_updated" task_state_updated = "task_state_updated"
@ -245,6 +253,7 @@ EventTypeMap = {
EventType.scaleset_created: EventScalesetCreated, EventType.scaleset_created: EventScalesetCreated,
EventType.scaleset_deleted: EventScalesetDeleted, EventType.scaleset_deleted: EventScalesetDeleted,
EventType.scaleset_failed: EventScalesetFailed, EventType.scaleset_failed: EventScalesetFailed,
EventType.scaleset_state_updated: EventScalesetStateUpdated,
EventType.task_created: EventTaskCreated, EventType.task_created: EventTaskCreated,
EventType.task_failed: EventTaskFailed, EventType.task_failed: EventTaskFailed,
EventType.task_state_updated: EventTaskStateUpdated, EventType.task_state_updated: EventTaskStateUpdated,