|
8 | 8 | import botocore.exceptions
|
9 | 9 | from aiobotocore.session import ClientCreatorContext
|
10 | 10 | from aiocache import cached # type: ignore[import-untyped]
|
11 |
| -from pydantic import ByteSize, PositiveInt, parse_obj_as |
| 11 | +from pydantic import ByteSize, PositiveInt |
12 | 12 | from servicelib.logging_utils import log_context
|
13 | 13 | from settings_library.ec2 import EC2Settings
|
14 | 14 | from types_aiobotocore_ec2 import EC2Client
|
|
29 | 29 | EC2Tags,
|
30 | 30 | Resources,
|
31 | 31 | )
|
32 |
| -from .utils import compose_user_data |
| 32 | +from .utils import compose_user_data, ec2_instance_data_from_aws_instance |
33 | 33 |
|
34 | 34 | _logger = logging.getLogger(__name__)
|
35 | 35 |
|
@@ -193,27 +193,17 @@ async def launch_instances(
|
193 | 193 | await waiter.wait(InstanceIds=instance_ids)
|
194 | 194 | _logger.info("instances %s exists now.", instance_ids)
|
195 | 195 |
|
196 |
| - # get the private IPs |
| 196 | + # NOTE: waiting for pending ensure we get all the IPs back |
197 | 197 | described_instances = await self.client.describe_instances(
|
198 | 198 | InstanceIds=instance_ids
|
199 | 199 | )
|
| 200 | + assert "Instances" in described_instances["Reservations"][0] # nosec |
200 | 201 | instance_datas = [
|
201 |
| - EC2InstanceData( |
202 |
| - launch_time=instance["LaunchTime"], |
203 |
| - id=instance["InstanceId"], |
204 |
| - aws_private_dns=instance["PrivateDnsName"], |
205 |
| - aws_public_ip=instance.get("PublicIpAddress", None), |
206 |
| - type=instance["InstanceType"], |
207 |
| - state=instance["State"]["Name"], |
208 |
| - tags=parse_obj_as( |
209 |
| - EC2Tags, {tag["Key"]: tag["Value"] for tag in instance["Tags"]} |
210 |
| - ), |
211 |
| - resources=instance_config.type.resources, |
212 |
| - ) |
213 |
| - for instance in described_instances["Reservations"][0]["Instances"] |
| 202 | + await ec2_instance_data_from_aws_instance(self, i) |
| 203 | + for i in described_instances["Reservations"][0]["Instances"] |
214 | 204 | ]
|
215 | 205 | _logger.info(
|
216 |
| - "%s is available, happy computing!!", |
| 206 | + "%s is pending now, happy computing!!", |
217 | 207 | f"{instance_datas=}",
|
218 | 208 | )
|
219 | 209 | return instance_datas
|
@@ -245,38 +235,51 @@ async def get_instances(
|
245 | 235 | all_instances = []
|
246 | 236 | for reservation in instances["Reservations"]:
|
247 | 237 | assert "Instances" in reservation # nosec
|
248 |
| - for instance in reservation["Instances"]: |
249 |
| - assert "LaunchTime" in instance # nosec |
250 |
| - assert "InstanceId" in instance # nosec |
251 |
| - assert "PrivateDnsName" in instance # nosec |
252 |
| - assert "InstanceType" in instance # nosec |
253 |
| - assert "State" in instance # nosec |
254 |
| - assert "Name" in instance["State"] # nosec |
255 |
| - ec2_instance_types = await self.get_ec2_instance_capabilities( |
256 |
| - {instance["InstanceType"]} |
257 |
| - ) |
258 |
| - assert len(ec2_instance_types) == 1 # nosec |
259 |
| - assert "Tags" in instance # nosec |
260 |
| - all_instances.append( |
261 |
| - EC2InstanceData( |
262 |
| - launch_time=instance["LaunchTime"], |
263 |
| - id=instance["InstanceId"], |
264 |
| - aws_private_dns=instance["PrivateDnsName"], |
265 |
| - aws_public_ip=instance.get("PublicIpAddress", None), |
266 |
| - type=instance["InstanceType"], |
267 |
| - state=instance["State"]["Name"], |
268 |
| - resources=ec2_instance_types[0].resources, |
269 |
| - tags=parse_obj_as( |
270 |
| - EC2Tags, |
271 |
| - {tag["Key"]: tag["Value"] for tag in instance["Tags"]}, |
272 |
| - ), |
273 |
| - ) |
274 |
| - ) |
| 238 | + all_instances.extend( |
| 239 | + [ |
| 240 | + await ec2_instance_data_from_aws_instance(self, i) |
| 241 | + for i in reservation["Instances"] |
| 242 | + ] |
| 243 | + ) |
275 | 244 | _logger.debug(
|
276 | 245 | "received: %s instances with %s", f"{len(all_instances)}", f"{state_names=}"
|
277 | 246 | )
|
278 | 247 | return all_instances
|
279 | 248 |
|
| 249 | + async def start_instances( |
| 250 | + self, instance_datas: Iterable[EC2InstanceData] |
| 251 | + ) -> list[EC2InstanceData]: |
| 252 | + try: |
| 253 | + instance_ids = [i.id for i in instance_datas] |
| 254 | + with log_context( |
| 255 | + _logger, |
| 256 | + logging.INFO, |
| 257 | + msg=f"starting instances {instance_ids}", |
| 258 | + ): |
| 259 | + await self.client.start_instances(InstanceIds=instance_ids) |
| 260 | + # wait for the instance to be in a pending state |
| 261 | + # NOTE: reference to EC2 states https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-lifecycle.html |
| 262 | + waiter = self.client.get_waiter("instance_exists") |
| 263 | + await waiter.wait(InstanceIds=instance_ids) |
| 264 | + _logger.info("instances %s exists now.", instance_ids) |
| 265 | + # NOTE: waiting for pending ensure we get all the IPs back |
| 266 | + aws_instances = await self.client.describe_instances( |
| 267 | + InstanceIds=instance_ids |
| 268 | + ) |
| 269 | + assert len(aws_instances["Reservations"]) == 1 # nosec |
| 270 | + assert "Instances" in aws_instances["Reservations"][0] # nosec |
| 271 | + return [ |
| 272 | + await ec2_instance_data_from_aws_instance(self, i) |
| 273 | + for i in aws_instances["Reservations"][0]["Instances"] |
| 274 | + ] |
| 275 | + except botocore.exceptions.ClientError as exc: |
| 276 | + if ( |
| 277 | + exc.response.get("Error", {}).get("Code", "") |
| 278 | + == "InvalidInstanceID.NotFound" |
| 279 | + ): |
| 280 | + raise EC2InstanceNotFoundError from exc |
| 281 | + raise # pragma: no cover |
| 282 | + |
280 | 283 | async def stop_instances(self, instance_datas: Iterable[EC2InstanceData]) -> None:
|
281 | 284 | try:
|
282 | 285 | with log_context(
|
|
0 commit comments