2525import time
2626from collections import defaultdict , namedtuple
2727from datetime import datetime , timedelta
28+ from typing import Dict , Optional
2829
2930from boto .ec2 import connect_to_region
3031from tornado import gen
5253AWS_AMI_IDS = {k : {} for k in AWS_REGIONS }
5354
5455
56+ # How long after est. run times to trigger the reaper
57+ REAPER_DELTA = timedelta (hours = 5 )
58+ # Force the reaper for run times less than
59+ REAPER_FORCE = timedelta (hours = 24 )
60+ REAPER_STATE = 'ThirdState'
61+
62+
5563def populate_ami_ids (aws_access_key_id = None , aws_secret_access_key = None ,
5664 port = None , owner_id = "595879546273" , use_filters = True ):
5765 """Populate all the AMI ID's with the latest CoreOS stable info.
@@ -355,7 +363,7 @@ def __init__(self, broker_id, access_key=None, secret_key=None,
355363 self .security = security
356364 self .user_data = user_data
357365 self ._instances = defaultdict (list )
358- self ._tag_filters = {"tag:Name" : "loads-%s" % self .broker_id ,
366+ self ._tag_filters = {"tag:Name" : "loads-%s* " % self .broker_id ,
359367 "tag:Project" : "loads" }
360368 self ._conns = {}
361369 self ._recovered = {}
@@ -493,7 +501,7 @@ def _locate_existing_instances(self, count, inst_type, region):
493501
494502 for inst in region_instances :
495503 if available_instance (inst ) and inst_type == inst .instance_type :
496- instances .append (inst )
504+ instances .append (inst )
497505 else :
498506 remaining .append (inst )
499507
@@ -518,12 +526,15 @@ async def _allocate_instances(self, conn, count, inst_type, region):
518526 return reservations .instances
519527
520528 async def request_instances (self ,
521- run_id ,
522- uuid ,
529+ run_id : str ,
530+ uuid : str ,
523531 count = 1 ,
524532 inst_type = "t1.micro" ,
525533 region = "us-west-2" ,
526- allocate_missing = True ):
534+ allocate_missing = True ,
535+ plan : Optional [str ] = None ,
536+ owner : Optional [str ] = None ,
537+ run_max_time : Optional [int ] = None ):
527538 """Allocate a collection of instances.
528539
529540 :param run_id: Run ID for these instances
@@ -535,6 +546,10 @@ async def request_instances(self,
535546 If there's insufficient existing instances for this uuid,
536547 whether existing or new instances should be allocated to the
537548 collection.
549+ :param plan: Name of the instances' plan
550+ :param owner: Owner name of the instances
551+ :param run_max_time: Maximum expected run-time of instances in
552+ seconds
538553 :returns: Collection of allocated instances
539554 :rtype: :class:`EC2Collection`
540555
@@ -563,26 +578,33 @@ async def request_instances(self,
563578 if num > 0 :
564579 new_instances = await self ._allocate_instances (
565580 conn , num , inst_type , region )
566- logger .debug ("Allocated instances: %s" , new_instances )
581+ logger .debug ("Allocated instances%s: %s" ,
582+ " (Owner: %s)" % owner if owner else "" ,
583+ new_instances )
567584 instances .extend (new_instances )
568585
569586 # Tag all the instances
570587 if self .use_filters :
571- # Sometimes, we can get instance data back before the AWS API fully
572- # recognizes it, so we wait as needed.
588+ tags = {
589+ "Name" : "loads-{}{}" .format (self .broker_id ,
590+ "-" + plan if plan else "" ),
591+ "Project" : "loads" ,
592+ "RunId" : run_id ,
593+ "Uuid" : uuid ,
594+ }
595+ if owner :
596+ tags ["Owner" ] = owner
597+ if run_max_time is not None :
598+ self ._tag_for_reaping (tags , run_max_time )
599+
600+ # Sometimes, we can get instance data back before the AWS
601+ # API fully recognizes it, so we wait as needed.
573602 async def tag_instance (instance ):
574603 retries = 0
575604 while True :
576605 try :
577606 await self ._run_in_executor (
578- conn .create_tags , [instance .id ],
579- {
580- "Name" : "loads-%s" % self .broker_id ,
581- "Project" : "loads" ,
582- "RunId" : run_id ,
583- "Uuid" : uuid
584- }
585- )
607+ conn .create_tags , [instance .id ], tags )
586608 break
587609 except :
588610 if retries > 5 :
@@ -592,6 +614,23 @@ async def tag_instance(instance):
592614 await gen .multi ([tag_instance (x ) for x in instances ])
593615 return EC2Collection (run_id , uuid , conn , instances , self ._loop )
594616
617+ def _tag_for_reaping (self ,
618+ tags : Dict [str , str ],
619+ run_max_time : int ) -> None :
620+ """Tag an instance for the mozilla-services reaper
621+
622+ Set instances to stop after run_max_time + REAPER_DELTA
623+
624+ """
625+ now = datetime .utcnow ()
626+ reap = now + timedelta (seconds = run_max_time ) + REAPER_DELTA
627+ tags ['REAPER' ] = "{}|{:{dfmt}}" .format (
628+ REAPER_STATE , reap , dfmt = "%Y-%m-%d %I:%M%p UTC" )
629+ if reap < now + REAPER_FORCE :
630+ # the reaper ignores instances younger than REAPER_FORCE
631+ # unless forced w/ REAP_ME
632+ tags ['REAP_ME' ] = ""
633+
595634 async def release_instances (self , collection ):
596635 """Return a collection of instances to the pool.
597636
0 commit comments