Skip to content

Commit cd35f28

Browse files
committed
Version 1.1
Version 1.1 updates
1 parent ac4f735 commit cd35f28

15 files changed

+372
-122
lines changed

CODE_OF_CONDUCT.md

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
## Code of Conduct
2+
This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
3+
For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
4+
opensource-codeofconduct@amazon.com with any additional questions or comments.

CONTRIBUTING.md

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Contributing Guidelines
2+
3+
Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional
4+
documentation, we greatly value feedback and contributions from our community.
5+
6+
Please read through this document before submitting any issues or pull requests to ensure we have all the necessary
7+
information to effectively respond to your bug report or contribution.
8+
9+
10+
## Reporting Bugs/Feature Requests
11+
12+
We welcome you to use the GitHub issue tracker to report bugs or suggest features.
13+
14+
When filing an issue, please check [existing open](https://github.com/awslabs/iot-device-simulator/issues), or [recently closed](https://github.com/awslabs/iot-device-simulator/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already
15+
reported the issue. Please try to include as much information as you can. Details like these are incredibly useful:
16+
17+
* A reproducible test case or series of steps
18+
* The version of our code being used
19+
* Any modifications you've made relevant to the bug
20+
* Anything unusual about your environment or deployment
21+
22+
23+
## Contributing via Pull Requests
24+
Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:
25+
26+
1. You are working against the latest source on the *master* branch.
27+
2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already.
28+
3. You open an issue to discuss any significant work - we would hate for your time to be wasted.
29+
30+
To send us a pull request, please:
31+
32+
1. Fork the repository.
33+
2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change.
34+
3. Ensure local tests pass.
35+
4. Commit to your fork using clear commit messages.
36+
5. Send us a pull request, answering any default questions in the pull request interface.
37+
6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.
38+
39+
GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and
40+
[creating a pull request](https://help.github.com/articles/creating-a-pull-request/).
41+
42+
43+
## Finding contributions to work on
44+
Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/awslabs/iot-device-simulator/labels/help%20wanted) issues is a great place to start.
45+
46+
47+
## Code of Conduct
48+
This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
49+
For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
50+
opensource-codeofconduct@amazon.com with any additional questions or comments.
51+
52+
53+
## Security issue notifications
54+
If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue.
55+
56+
57+
## Licensing
58+
59+
See the [LICENSE](https://github.com/awslabs/iot-device-simulator/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution.
60+
61+
We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes.

deployment/build-s3-dist.sh

-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ echo "mkdir -p dist"
1616
mkdir -p dist
1717

1818
mv instance-scheduler-latest.template dist/instance-scheduler.template
19-
mv instance-scheduler-remote-latest.template dist/instance-scheduler-remote.template
20-
mv instance-schedule-forward-events-latest.template dist/instance-schedule-forward-events.template
2119
mv scheduler-cli-latest.zip dist/scheduler-cli.zip
2220
mv instance-scheduler-`cat ../source/code/version.txt`.zip dist
2321
rm instance-scheduler-`cat ../source/code/version.txt`.template
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
{
2+
"AWSTemplateFormatVersion": "2010-09-09",
3+
"Description": "ECS and RDS scheduler cross account role, version 2.2.2.0",
4+
"Parameters": {
5+
"InstanceSchedulerAccount": {
6+
"Type": "String",
7+
"AllowedPattern": "(^[0-9]{12}$)",
8+
"ConstraintDescription": "Account number is a 12 digit number",
9+
"Description": "Account number of Instance Scheduler account to give access to manage EC2 and RDS Instances in this account."
10+
}
11+
},
12+
"Metadata": {
13+
"AWS::CloudFormation::Interface": {
14+
"ParameterGroups": [{
15+
"Label": {
16+
"default": "Account"
17+
},
18+
"Parameters": [
19+
"InstanceSchedulerAccount"
20+
]
21+
}],
22+
"ParameterLabels": {
23+
"InstanceSchedulerAccount": {
24+
"default": "Primary account"
25+
}
26+
}
27+
}
28+
},
29+
"Resources": {
30+
"EC2SchedulerCrossAccountRole": {
31+
"Type": "AWS::IAM::Role",
32+
"Properties": {
33+
"AssumeRolePolicyDocument": {
34+
"Version": "2012-10-17",
35+
"Statement": [{
36+
"Effect": "Allow",
37+
"Principal": {
38+
"AWS": {
39+
"Fn::Join": [
40+
"", [
41+
"arn:aws:iam::", {
42+
"Ref": "InstanceSchedulerAccount"
43+
},
44+
":root"
45+
]
46+
]
47+
},
48+
"Service": "lambda.amazonaws.com"
49+
},
50+
"Action": [
51+
"sts:AssumeRole"
52+
]
53+
}]
54+
},
55+
"Path": "/",
56+
"Policies": [{
57+
"PolicyName": "EC2InstanceSchedulerRemote",
58+
"PolicyDocument": {
59+
"Version": "2012-10-17",
60+
"Statement": [{
61+
"Effect": "Allow",
62+
"Action": [
63+
"ec2:DescribeInstances",
64+
"ec2:StartInstances",
65+
"ec2:StopInstances",
66+
"ec2:ModifyInstanceAttribute",
67+
"ec2:CreateTags",
68+
"ec2:DeleteTags"
69+
],
70+
"Resource": [
71+
"*"
72+
]
73+
}, {
74+
"Effect": "Allow",
75+
"Action": [
76+
"rds:DescribeDBInstances",
77+
"rds:DescribeDBSnapshots",
78+
"rds:StartDBInstance",
79+
"rds:StopDBInstance",
80+
"rds:AddTagsToResource",
81+
"rds:RemoveTagsFromResource",
82+
"rds:DeleteDBSnapshot"
83+
],
84+
"Resource": [
85+
"*"
86+
]
87+
}, {
88+
"Effect": "Allow",
89+
"Action": [
90+
"tag:GetResources"
91+
],
92+
"Resource": [
93+
"*"
94+
]
95+
}]
96+
}
97+
}]
98+
}
99+
}
100+
},
101+
"Outputs": {
102+
"CrossAccountRole": {
103+
"Value": {
104+
"Fn::GetAtt": [
105+
"EC2SchedulerCrossAccountRole",
106+
"Arn"
107+
]
108+
},
109+
"Description": "Arn for cross account role for Instance scheduler, add this arn to the list of crossaccount roles (CrossAccountRoles) parameter of the Instance Scheduler template."
110+
}
111+
}
112+
}

source/code/cli/scheduler_cli/scheduler_cli.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
HELP_PERIOD_DESCRIPTION = "Description for the period"
3939
HELP_PERIOD_ENDTIME = "End time of the period in format hh:mm"
4040
HELP_PERIOD_MONTH_DAYS = "Calendar monthdays of the period"
41+
HELP_PERIOD_MONTHS = "Months of the period"
4142
HELP_PERIOD_NAME = "Name of the period"
4243
HELP_PERIOD_WEEKDAYS = "Weekdays of the period"
4344
HELP_QUERY = "JMESPath query to transform or filter the result"
@@ -70,6 +71,7 @@
7071
PARAM_RETAINED_RUNNING = "--retain-running"
7172
PARAM_METRICS = "--metrics"
7273
PARAM_MONTHDAYS = "--monthdays"
74+
PARAM_MONTHS = "--months"
7375
PARAM_OVERRIDE = "--override-status"
7476
PARAM_PERIODS = "--periods"
7577
PARAM_STARTDATE = "--startdate"
@@ -110,7 +112,7 @@ def handle_command(args, command):
110112
lambda_resource = cloudformation_client.describe_stack_resource(
111113
StackName=args.stack, LogicalResourceId="Main").get("StackResourceDetail", None)
112114

113-
lambda_client = boto3.client("lambda")
115+
lambda_client = _service_client("lambda", region=args.region)
114116

115117
event = {
116118
"source": EVENT_SOURCE,
@@ -163,6 +165,7 @@ def add_period_arguments(period_parser):
163165
period_parser.add_argument(PARAM_DESCRIPTION, help=HELP_PERIOD_DESCRIPTION)
164166
period_parser.add_argument(PARAM_ENDTIME, help=HELP_PERIOD_ENDTIME)
165167
period_parser.add_argument(PARAM_MONTHDAYS, help=HELP_PERIOD_MONTH_DAYS)
168+
period_parser.add_argument(PARAM_MONTHS,help=HELP_PERIOD_MONTHS)
166169
period_parser.add_argument(PARAM_NAME, required=True, help=HELP_PERIOD_NAME)
167170
period_parser.add_argument(PARAM_WEEKDAYS, help=HELP_PERIOD_WEEKDAYS)
168171

@@ -254,6 +257,4 @@ def build_describe_schedule_usage_parser():
254257
def main():
255258
parser = build_parser()
256259
p = parser.parse_args(sys.argv[1:])
257-
sys.exit( p.func(p, p.command))
258-
259-
260+
sys.exit(p.func(p, p.command))

source/code/configuration/instance_schedule.py

+28-8
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def __str__(self):
109109
s += "\n".join(attributes)
110110
return s
111111

112-
def get_desired_state(self, instance, logger=None, dt=None):
112+
def get_desired_state(self, instance, logger=None, dt=None, check_adjacent_periods =True):
113113
"""
114114
Test if an instance should be running at a specific moment in this schedule
115115
:param instance: the instance to test
@@ -202,12 +202,7 @@ def handle_override_status():
202202
self._log_debug(DEBUG_USED_TIME_FOR_SCHEDULE, localized_time.strftime("%c"))
203203

204204
# get the desired state for every period in the schedule
205-
periods_with_desired_states = [
206-
{
207-
"period": p["period"], "instancetype": p.get("instancetype", None),
208-
"state": p["period"].get_desired_state(self._logger, localized_time)
209-
}
210-
for p in self.periods]
205+
periods_with_desired_states = self.get_periods_with_desired_states(localized_time)
211206

212207
# get periods from the schema that have a running state
213208
periods_with_running_state = filter(lambda period: period["state"] == InstanceSchedule.STATE_RUNNING,
@@ -220,8 +215,30 @@ def handle_override_status():
220215
if any(period_with_any_state):
221216
return handle_any_state()
222217

218+
if len(periods_with_desired_states) > 1 and check_adjacent_periods:
219+
self._log_debug("Checking for adjacent running periods at current time")
220+
self._log_debug("Checking states for previous minute")
221+
last_minute_running_periods = [p for p in self.get_periods_with_desired_states(localized_time - timedelta(minutes=1)) if p["state"] == InstanceSchedule.STATE_RUNNING]
222+
self._log_debug("Running period(s) for previous minute {}", ",".join([p["period"].name for p in last_minute_running_periods]))
223+
if len(last_minute_running_periods) > 0:
224+
self._log_debug("Checking states for next minute")
225+
next_minute_running_periods = [p for p in self.get_periods_with_desired_states(localized_time + timedelta(minutes=1)) if p["state"] == InstanceSchedule.STATE_RUNNING]
226+
self._log_debug("Running period(s) for next minute {}", ",".join([p["period"].name for p in next_minute_running_periods]))
227+
if len(next_minute_running_periods):
228+
self._log_debug("Adjacent periods found, keep instance in running state")
229+
return handle_running_state(instance, last_minute_running_periods)
230+
223231
return handle_stopped_state()
224232

233+
def get_periods_with_desired_states(self, time):
234+
periods_with_desired_states = [
235+
{
236+
"period": p["period"], "instancetype": p.get("instancetype", None),
237+
"state": p["period"].get_desired_state(self._logger, time)
238+
}
239+
for p in self.periods]
240+
return periods_with_desired_states
241+
225242
def get_usage(self, start_dt, stop_dt=None, instance=None, logger=None):
226243
"""
227244
Get running periods for a schedule in a period
@@ -280,7 +297,7 @@ def make_period(started_dt, stopped_dt, inst_type):
280297
instance_type = None
281298
inst = instance or as_namedtuple("Instance", {"instance_str": "instance", "allow_resize": False})
282299
for tm in sorted(list(timeline)):
283-
desired_state, instance_type, period = self.get_desired_state(inst, self._logger, tm)
300+
desired_state, instance_type, period = self.get_desired_state(inst, self._logger, tm, False)
284301

285302
if current_state != desired_state:
286303
if desired_state == InstanceSchedule.STATE_RUNNING:
@@ -289,6 +306,9 @@ def make_period(started_dt, stopped_dt, inst_type):
289306
starting_period = period
290307
elif desired_state == InstanceSchedule.STATE_STOPPED:
291308
stopped = tm
309+
desired_state_with_adj_check, _, __ = self.get_desired_state(inst, self._logger, tm, True)
310+
if desired_state_with_adj_check == InstanceSchedule.STATE_RUNNING:
311+
stopped += timedelta(minutes=1)
292312
if current_state == InstanceSchedule.STATE_RUNNING:
293313
current_state = InstanceSchedule.STATE_STOPPED
294314
running_periods[starting_period] = (make_period(started, stopped, instance_type))

source/code/configuration/running_period.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -125,14 +125,14 @@ def check_time(dt):
125125
return desired_state
126126
elif self.begintime is None:
127127
# just the end time, stopped if later than that time
128-
desired_state = InstanceSchedule.STATE_STOPPED if t > self.endtime else InstanceSchedule.STATE_ANY
128+
desired_state = InstanceSchedule.STATE_STOPPED if t >= self.endtime else InstanceSchedule.STATE_ANY
129129
self._log_debug(DEBUG_CHECK_DT_STOP_TIME,
130130
check_running_state_str(desired_state), ts,
131131
"before" if desired_state == InstanceSchedule.STATE_ANY else "after",
132132
time_str(self.endtime), desired_state)
133133
return desired_state
134134

135-
elif self.endtime is None:
135+
elif self.begintime is not None and self.endtime is None:
136136
# just the start time, running if later that that time
137137
desired_state = InstanceSchedule.STATE_RUNNING if t >= self.begintime else InstanceSchedule.STATE_ANY
138138
self._log_debug(DEBUG_CHECK_DT_START_TIME,

source/code/configuration/scheduler_config.py

+4-26
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ def __init__(self, scheduled_services, tag_name, regions, default_timezone, sche
6464
self.schedule_lambda_account = schedule_lambda_account
6565
self.scheduled_services = scheduled_services
6666
self._service_settings = None
67-
self._start_tags = started_tags
68-
self._stop_tags = stopped_tags
67+
self.started_tags = [] if started_tags in ["" or None] else self.tag_list(self.build_tags_from_template(started_tags))
68+
self.stopped_tags = [] if stopped_tags in ["" or None] else self.tag_list(self.build_tags_from_template(stopped_tags))
6969

7070
def get_schedule(self, name):
7171
"""
@@ -75,28 +75,6 @@ def get_schedule(self, name):
7575
"""
7676
return self.schedules[name] if name in self.schedules else None
7777

78-
@property
79-
def started_tags(self):
80-
"""
81-
Return tags to set to started instances
82-
:return: tags for started instances
83-
"""
84-
if self._start_tags in ["" or None]:
85-
return []
86-
else:
87-
return self.tag_list(self.build_tags_from_template(self._start_tags))
88-
89-
@property
90-
def stopped_tags(self):
91-
"""
92-
Return tags to set to stopped instances
93-
:return: tags for stopped instances
94-
"""
95-
if self._stop_tags in ["" or None]:
96-
return []
97-
else:
98-
return self.tag_list(self.build_tags_from_template(self._stop_tags))
99-
10078
@classmethod
10179
def build_tags_from_template(cls, tags_str, tag_variables=None):
10280

@@ -143,8 +121,8 @@ def __str__(self):
143121
str(self.trace),
144122
str(self.use_metrics),
145123
", ".join(self.regions),
146-
self._start_tags,
147-
self._stop_tags,
124+
str(self.started_tags),
125+
str(self.stopped_tags),
148126
str(self.schedule_lambda_account),
149127
", ".join(self.cross_account_roles))
150128

0 commit comments

Comments
 (0)