Skip to content

Commit 1b3ed3d

Browse files
committed
Add incomplete tests for startCluster
- add test for generate_ecs_config - add test for checking spot fleet config file - modify example fleet file KeyName to match config.py as stated in docs
1 parent 6058212 commit 1b3ed3d

File tree

4 files changed

+202
-8
lines changed

4 files changed

+202
-8
lines changed

files/exampleFleet_us-east-1.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"LaunchSpecifications": [
66
{
77
"ImageId": "ami-fad25980",
8-
"KeyName": "your_key_file_name",
8+
"KeyName": "your-key-file",
99
"IamInstanceProfile": {
1010
"Arn": "arn:aws:iam::XXXXXXXXXXXX:instance-profile/ecsInstanceRole"
1111
},

files/exampleFleet_us-west-2.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"LaunchSpecifications": [
66
{
77
"ImageId": "ami-c9c87cb1",
8-
"KeyName": "your_key_file_name",
8+
"KeyName": "your-key-file",
99
"IamInstanceProfile": {
1010
"Arn": "arn:aws:iam::XXXXXXXXXXXX:instance-profile/ecsInstanceRole"
1111
},

tests/conftest.py

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55
import pytest
66
import boto3
7-
from moto import mock_sqs, mock_ecs
7+
from moto import mock_sqs, mock_ecs, mock_s3
88

99
import run
10-
from config import AWS_REGION, AWS_PROFILE
10+
from config import AWS_REGION, AWS_PROFILE, AWS_BUCKET, APP_NAME
1111

1212

1313
# WARNING: Do not import a module here or in any of the tests
@@ -32,6 +32,8 @@
3232

3333
JOB_FILE = Path(__file__).parent.parent / "files/exampleJob.json"
3434
FLEET_FILE = Path(__file__).parent.parent / "files/exampleFleet_us-east-1.json"
35+
# does not exist unless startCluster has been run
36+
MONITOR_FILE = Path(__file__).parent.parent / f"files/{APP_NAME}SpotFleetRequestId.json"
3537

3638

3739
@pytest.fixture(autouse=True, scope="session")
@@ -95,13 +97,34 @@ def aws_config(monkeypatch, tmp_path):
9597
@pytest.fixture(scope="function")
9698
def sqs():
9799
with mock_sqs():
98-
yield boto3.client("sqs", region_name=os.environ["AWS_DEFAULT_REGION"])
100+
yield boto3.client("sqs", region_name=AWS_REGION)
99101

100102

101103
@pytest.fixture(scope="function")
102104
def ecs():
103105
with mock_ecs():
104-
yield boto3.client("ecs", os.environ["AWS_DEFAULT_REGION"])
106+
yield boto3.client("ecs", region_name=AWS_REGION)
107+
108+
109+
@pytest.fixture(scope="function")
110+
def s3():
111+
with mock_s3():
112+
yield boto3.client("s3", region_name=AWS_REGION)
113+
114+
115+
@pytest.fixture(scope="function")
116+
def protect_monitor_file():
117+
# read in the current contents, if any
118+
curr = None
119+
if MONITOR_FILE.exists():
120+
curr = MONITOR_FILE.read_text()
121+
122+
# do something that may or may not write to the file
123+
yield MONITOR_FILE
124+
125+
# clean up by putting the original contents back, if any
126+
if curr:
127+
MONITOR_FILE.write_text(curr)
105128

106129

107130
# Below functions are fixtures that run steps 1 - 4
@@ -116,6 +139,8 @@ def ecs():
116139
# Therefor we return a non-mocked callback, the tests are decorated with
117140
# mocks, and then the tests invoke the callback returned by the fixture.
118141

142+
143+
# mock sqs and ecs before running cb
119144
@pytest.fixture(scope="function")
120145
def run_setup(aws_config):
121146
def f():
@@ -124,12 +149,38 @@ def f():
124149
return f
125150

126151

152+
# mock sqs and ecs before running cb
127153
@pytest.fixture(scope="function")
128154
def run_submitJob(run_setup, monkeypatch):
129-
monkeypatch.setattr(sys, "argv", ["run.py", "submitJob", str(JOB_FILE)])
130-
131155
def f():
156+
# don't put this outside of the callback, else it may be overwritten
157+
monkeypatch.setattr(sys, "argv", ["run.py", "submitJob", str(JOB_FILE)])
158+
132159
run_setup()
133160
run.submitJob()
161+
print('x')
162+
163+
return f
164+
165+
166+
# mock sqs, ecs, s3 and ec2 before running cb
167+
@pytest.fixture(scope="function")
168+
def run_startCluster(run_submitJob, monkeypatch, protect_monitor_file):
169+
def f():
170+
s3 = boto3.client('s3')
171+
172+
if (AWS_REGION == "us-east-1"):
173+
# 'us-east-1' is the default region for S3 buckets
174+
# and is not a vallid arg for "LocationConstraint"
175+
s3.create_bucket(Bucket=AWS_BUCKET)
176+
else:
177+
s3.create_bucket(Bucket=AWS_BUCKET, CreateBucketConfiguration={"LocationConstraint": AWS_REGION})
178+
179+
run_submitJob()
180+
181+
# don't put this outside of the callback, else it may be overwritten
182+
monkeypatch.setattr(sys, "argv", ["run.py", "startCluster", str(FLEET_FILE)])
183+
184+
run.startCluster()
134185

135186
return f

tests/test_startCluster.py

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import pytest
2+
from moto import mock_ecs, mock_sqs, mock_s3, mock_ec2
3+
import boto3
4+
5+
import run
6+
import config
7+
8+
9+
# startCluster will:
10+
# send a spot fleet request to moto-mocked-aws
11+
# create a APP_NAMESpotFleetRequestId.json file
12+
# once spot fleet request is ready (instantly), creat log groups for your log streams to go in
13+
# DS will ask aws moto-mocked-aws to place Docker containers onto the spot fleet instances
14+
# job will begin instantly
15+
16+
class TestGenerateECSConfig:
17+
@mock_ecs
18+
@mock_sqs
19+
def test_generate_ecs_config(self, s3, run_submitJob, tmp_path):
20+
if (config.AWS_REGION == "us-east-1"):
21+
# 'us-east-1' is the default region for S3 buckets
22+
# and is not a vallid arg for "LocationConstraint"
23+
s3.create_bucket(Bucket=config.AWS_BUCKET)
24+
else:
25+
s3.create_bucket(Bucket=config.AWS_BUCKET, CreateBucketConfiguration={"LocationConstraint": config.AWS_REGION})
26+
27+
run_submitJob()
28+
29+
res_endpoint = run.generateECSconfig(config.ECS_CLUSTER, config.APP_NAME, config.AWS_BUCKET, s3)
30+
31+
expected_key = f"ecsconfigs/{config.APP_NAME}_ecs.config"
32+
expected_file_path = tmp_path / "configtemp.config"
33+
expected_file_endpoint = f"s3://{config.AWS_BUCKET}/{expected_key}"
34+
35+
assert res_endpoint == expected_file_endpoint
36+
37+
# create the file object to write to
38+
expected_file_path.touch()
39+
40+
with expected_file_path.open('wb') as f:
41+
s3.download_fileobj(config.AWS_BUCKET, expected_key, f)
42+
43+
res_file = expected_file_path.read_text()
44+
45+
assert res_file == f"ECS_CLUSTER={config.ECS_CLUSTER}\nECS_AVAILABLE_LOGGING_DRIVERS=[\"json-file\",\"awslogs\"]"
46+
47+
48+
49+
class EarlyTermination(Exception):
50+
...
51+
52+
def hijack_ec2(real_client):
53+
"""
54+
Patches boto3.client so that an invocation of 'ec2' will cause an
55+
EarlyTermination exception, allowing inspection and testing of the
56+
stack frame up until that point.
57+
"""
58+
def f(*args, **kwargs):
59+
if (args[0] == 'ec2'):
60+
raise EarlyTermination("early termination")
61+
62+
return real_client(*args, **kwargs)
63+
64+
return f
65+
66+
67+
class TestSpotFleetConfig:
68+
@mock_ecs
69+
@mock_sqs
70+
@mock_s3
71+
def test_spot_fleet_config(self, run_startCluster, monkeypatch):
72+
monkeypatch.setattr(boto3, "client", hijack_ec2(boto3.client))
73+
with pytest.raises(EarlyTermination) as e_info:
74+
run_startCluster()
75+
76+
spot_fleet_config_res = None
77+
for tb in e_info.traceback:
78+
if (tb.name == "startCluster"):
79+
spot_fleet_config_res = tb.frame.f_locals["spotfleetConfig"]
80+
81+
assert spot_fleet_config_res is not None
82+
83+
# For config file requirements, see:
84+
# https://distributedscience.github.io/Distributed-Something/step_3_start_cluster.html#configuring-your-spot-fleet-request
85+
86+
assert "IamFleetRole" in spot_fleet_config_res
87+
assert spot_fleet_config_res["IamFleetRole"].startswith("arn:aws:iam::")
88+
89+
assert "ValidFrom" in spot_fleet_config_res
90+
assert "ValidUntil" in spot_fleet_config_res
91+
assert "TargetCapacity" in spot_fleet_config_res
92+
assert spot_fleet_config_res["TargetCapacity"] == config.CLUSTER_MACHINES
93+
assert "SpotPrice" in spot_fleet_config_res
94+
assert float(spot_fleet_config_res["SpotPrice"]) == pytest.approx(config.MACHINE_PRICE, rel=1e-2)
95+
96+
launch_specs = spot_fleet_config_res["LaunchSpecifications"]
97+
for i in range(len(launch_specs)):
98+
assert "IamInstanceProfile" in launch_specs[i]
99+
assert "Arn" in launch_specs[i]["IamInstanceProfile"]
100+
assert launch_specs[i]["IamInstanceProfile"]["Arn"].startswith("arn:aws:iam::")
101+
102+
assert "KeyName" in launch_specs[i]
103+
assert launch_specs[i]["KeyName"] == config.SSH_KEY_NAME[:-4]
104+
105+
assert "ImageId" in launch_specs[i]
106+
assert launch_specs[i]["ImageId"].startswith("ami-")
107+
108+
assert "NetworkInterfaces" in launch_specs[i]
109+
net_intfcs = launch_specs[i]["NetworkInterfaces"]
110+
for j in range(len(net_intfcs)):
111+
assert "SubnetId" in net_intfcs[j]
112+
assert net_intfcs[j]["SubnetId"].startswith("subnet-")
113+
114+
assert "Groups" in net_intfcs[j]
115+
grps = net_intfcs[j]["Groups"]
116+
for k in range(len(grps)):
117+
assert grps[k].startswith("sg-")
118+
119+
assert "BlockDeviceMappings" in launch_specs[i]
120+
bdms = launch_specs[i]["BlockDeviceMappings"]
121+
122+
assert "Ebs" in bdms[0]
123+
assert "SnapshotId" in bdms[0]["Ebs"]
124+
assert bdms[0]["Ebs"]["SnapshotId"].startswith("snap-")
125+
126+
assert "Ebs" in bdms[1]
127+
assert "VolumeSize" in bdms[1]["Ebs"]
128+
assert bdms[1]["Ebs"]["VolumeSize"] == config.EBS_VOL_SIZE
129+
130+
assert "InstanceType" in launch_specs[i]
131+
assert launch_specs[i]["InstanceType"] == config.MACHINE_TYPE[i]
132+
133+
assert "UserData" in launch_specs[i]
134+
135+
136+
class TestStartCluster:
137+
@mock_ecs
138+
@mock_sqs
139+
@mock_s3
140+
@mock_ec2
141+
@pytest.mark.skip(reason="not implemented yet")
142+
def test_start_cluster(self, run_startCluster):
143+
run_startCluster()

0 commit comments

Comments
 (0)