Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
36ac89e
Update locust
stephenfuqua Feb 22, 2023
8b012f6
Remove unused kwargs
stephenfuqua Feb 22, 2023
122ce9a
This has been wrong since at least DS 3.2.0
stephenfuqua Feb 22, 2023
338a68b
Location header handling that works for Meadowlark
stephenfuqua Feb 23, 2023
4050060
Install pytest-describe
stephenfuqua Feb 23, 2023
4d62277
Rewrite using pytest-describe
stephenfuqua Feb 23, 2023
94e671a
Test for building descriptors
stephenfuqua Feb 23, 2023
9821d5f
Type ignore in clients
stephenfuqua Feb 24, 2023
d57fa1c
this descriptor is part of the natural key and shouldn't be changeable
stephenfuqua Feb 27, 2023
b004ef7
Fixing modeling mistakes that the ODS/API would forgive, but meadowla…
stephenfuqua Feb 27, 2023
6affa59
Merge remote-tracking branch 'origin/PERF-286' into RND-533
andonyns May 30, 2023
ee8a404
[RND-533] Skipping total count request when there was a failure to av…
andonyns Jun 7, 2023
8794e5d
Fixing poetry lock after merge
andonyns Jun 9, 2023
be503b2
Changing performance documentation to match allowed values
andonyns Jun 9, 2023
de0ab62
Adjusting env example
andonyns Jun 12, 2023
cd83b16
Merge branch 'main' into meadowlark-updates
andonyns Aug 9, 2023
e75e520
Merge branch 'main' into meadowlark-updates
andonyns Jan 10, 2024
da73627
Adding additional parameter to include id in body, required by Meadow…
andonyns Jan 12, 2024
4034a4f
Adjustments to run against Meadowlark
andonyns Jan 17, 2024
9c375ad
Merge branch 'main' into meadowlark-updates
andonyns Jan 17, 2024
79580dc
Adjustments
andonyns Jan 17, 2024
ce353d8
Linting adjustments
andonyns Jan 18, 2024
f59e76f
Adjustments when running ODS
andonyns Jan 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
* text eol=lf

*.md text
*.txt text
*.py text
Expand Down
12 changes: 7 additions & 5 deletions src/edfi-paging-test/edfi_paging_test/performance_tester.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,14 @@ def _generate_output_reports(args: MainArguments) -> None:

def fetch_resource(request_client: RequestClient, target_resource: str) -> None:
resources = request_client.get_all(target_resource)
total_count = request_client.get_total(target_resource)

if len(resources) != total_count:
logger.warn(
f"{target_resource}: expected {total_count} results, got: {len(resources)}"
)
if('error' not in resources):
total_count = request_client.get_total(target_resource)

if len(resources) != total_count:
logger.warn(
f"{target_resource}: expected {total_count} results, got: {len(resources)}"
)


def invalid_resources(
Expand Down
16 changes: 8 additions & 8 deletions src/edfi-performance-test/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Performance Tests

Provides performance metrics for the ODS / API SIS Certification endpoints aimed at analysis of
bottlenecks, and providing recommendations for server sizing for representative
agency simulation sizes.
Provides performance metrics for the ODS / API SIS Certification endpoints
aimed at analysis of bottlenecks, and providing recommendations for server
sizing for representative agency simulation sizes.

## Getting Started

Expand All @@ -24,8 +24,8 @@ directory:
poetry run python edfi_performance_test -b "https://localhost:54746" -k "testkey" -s "testsecret" -t "volume"
```

Test failure ratio is monitored throughout and run is stopped if more than 50% of the
tests experience failures.
Test failure ratio is monitored throughout and run is stopped if more than 50%
of the tests experience failures.

### Supported arguments

Expand All @@ -35,7 +35,7 @@ tests experience failures.
| `-k` or `--key` | yes (no default) | The web API OAuth key |
| `-s` or `--secret` | yes (no default) | The web API OAuth secret |
| `-i` or `--ignoreCertificateErrors` | no (default: false) | Ignore certificate errors |
| `-t` or `--testType` | no (default: PIPECLEAN) | Type of the performance tests to run: VOLUME, PIPECLEAN, CHANGE_QUERY |
| `-t` or `--testType` | no (default: pipeclean) | Type of the performance tests to run: volume, pipeclean, change_query |
| `-tl` or `--testList` | no (no default) | (Optional) List of test files to run - if not provided, all tests will be run |
| `-d` or `--deleteResources` | no (default: true) | Delete resources during test run |
| `-f` or `--failDeliberately` | no (default: true) | Deliberately introduce requests that result in failure |
Expand All @@ -48,8 +48,8 @@ tests experience failures.
| `-e` or `--localEducationOrganizationId` | no (default: 255901) | Override the default LEA education organization ID |

Each argument can also be set by environment variable, or by using as `.env`
file. See [.env.example](edfi_performance_test/.env.example). Arguments provided at
the command line override any arguments provided by environment variable.
file. See [.env.example](edfi_performance_test/.env.example). Arguments provided
at the command line override any arguments provided by environment variable.

### Dev Operations

Expand Down
7 changes: 6 additions & 1 deletion src/edfi-performance-test/edfi_performance_test/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ PERF_API_OAUTH_ENDPOINT="/oauth/token"

# Meadowlark
# PERF_API_PREFIX="/local/v3.3b/ed-fi"
# PERF_API_OAUTH_ENDPOINT="/local/api/oauth/token"
# PERF_API_OAUTH_ENDPOINT="/local/oauth/token"

# Set to true when using HTTP instead of HTTPS or when using self-signed certificates
IGNORE_TLS_CERTIFICATE=true

# PERF_TEST_LIST=[""]

# Meadowlark requires the ID in the body
INCLUDE_ID_IN_BODY=True
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@ class AccountClient(EdFiAPIClient):
}

def create_with_dependencies(self, **kwargs):
# TODO: this is the only reference to account_code_client anywhere in
# the code base, so the next line should fail if it is ever called.
account_code_reference = self.account_code_client.create_with_dependencies()
# The constructor for ed_fi_api_client dynamically creates the `xyz_client` property using `setattr`
account_code_reference = self.account_code_client.create_with_dependencies() # type: ignore
account_code_attrs = account_code_reference["attributes"]

edorg_id = account_code_attrs["educationOrganizationReference"][
Expand Down Expand Up @@ -56,10 +55,10 @@ class _AccountDependentMixin(object):
}

def create_with_dependencies(self, **kwargs):
account_reference = self.account_client.create_with_dependencies()
account_reference = self.account_client.create_with_dependencies() # type: ignore
account_identifier = account_reference["attributes"]["accountIdentifier"]

return self.create_using_dependencies(
return self.create_using_dependencies( # type: ignore
account_reference,
accountReference__accountIdentifier=account_identifier,
**kwargs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class AssessmentItemClient(EdFiAPIClient):

def create_with_dependencies(self, **kwargs):
# Create new assessment
assessment_reference = self.assessment_client.create_with_dependencies()
assessment_reference = self.assessment_client.create_with_dependencies() # type: ignore

# Create assessment item
return self.create_using_dependencies(
Expand All @@ -45,7 +45,7 @@ class ObjectiveAssessmentClient(EdFiAPIClient):

def create_with_dependencies(self, **kwargs):
# Create new assessment
assessment_reference = self.assessment_client.create_with_dependencies()
assessment_reference = self.assessment_client.create_with_dependencies() # type: ignore

# Create objective assessment
return self.create_using_dependencies(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class BellScheduleClient(EdFiAPIClient):

def create_with_dependencies(self, **kwargs):
# Create new class period
class_period_reference = self.class_period_client.create_with_dependencies()
class_period_reference = self.class_period_client.create_with_dependencies() # type: ignore

# Create bell schedule
return self.create_using_dependencies(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def create_with_dependencies(self, **kwargs):
"calendarCode", RandomSuffixAttribute("107SS111111")
)
# Create a calendar
calendar_reference = self.calendar_client.create_with_dependencies(
calendar_reference = self.calendar_client.create_with_dependencies( # type: ignore
schoolReference__schoolId=school_id, calendarCode=custom_calendar_code
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class CommunityProviderClient(EdFiAPIClient):
}

def create_with_dependencies(self, **kwargs):
org_reference = self.org_client.create_with_dependencies()
org_reference = self.org_client.create_with_dependencies() # type: ignore

return self.create_using_dependencies(
org_reference,
Expand All @@ -41,7 +41,7 @@ class CommunityProviderLicenseClient(EdFiAPIClient):
}

def create_with_dependencies(self, **kwargs):
provider_reference = self.provider_client.create_with_dependencies()
provider_reference = self.provider_client.create_with_dependencies() # type: ignore

return self.create_using_dependencies(
provider_reference,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,9 @@ def _get_all(self, resource):
response = self._get_response(
"get", list_endpoint, headers=self.get_headers(), name=list_endpoint
)

if not response.ok:
raise RuntimeError(f"Composite resource '{resource}' does not exist (404)")

self.log_response(response)
return json.loads(response.text)
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class CourseTranscriptClient(EdFiAPIClient):
def create_with_dependencies(self, **kwargs):
school_id = kwargs.pop("schoolId", SchoolClient.shared_elementary_school_id())

record_reference = self.record_client.create_with_dependencies(
record_reference = self.record_client.create_with_dependencies( # type: ignore
schoolId=school_id
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ class CourseOfferingClient(EdFiAPIClient):

def create_with_dependencies(self, **kwargs):
school_id = kwargs.pop("schoolId", SchoolClient.shared_elementary_school_id())
school_year = kwargs.get("schoolYear", 2014)
session_reference = self.session_client.create_with_dependencies(
school_year = kwargs.pop("schoolYear", 2014)

session_reference = self.session_client.create_with_dependencies( # type: ignore
schoolId=school_id, schoolYear=school_year
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def create_with_dependencies(self, **kwargs):
school_id = kwargs.pop("schoolId", SchoolClient.shared_elementary_school_id())

# Create staff
staff_reference = self.staff_client.create_with_dependencies(schoolId=school_id)
staff_reference = self.staff_client.create_with_dependencies(schoolId=school_id) # type: ignore

# Create discipline incident
return self.create_using_dependencies(
Expand All @@ -47,7 +47,7 @@ def create_with_dependencies(self, **kwargs):
# Create incident and association first
school_id = kwargs.pop("schoolId", SchoolClient.shared_elementary_school_id())

assoc_reference = self.assoc_client.create_with_dependencies(schoolId=school_id)
assoc_reference = self.assoc_client.create_with_dependencies(schoolId=school_id) # type: ignore

# Create discipline action
return self.create_using_dependencies(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import json
import logging
import re
from typing import Any, Dict
from typing import Any, Dict, Optional


import urllib3
Expand Down Expand Up @@ -118,6 +118,17 @@ def get_item(self, resource_id):
self.log_response(response)
return json.loads(response.text)

def _get_resource_id_from_location(self, headers: dict) -> Optional[str]:
header = ""
if "Location" in headers:
header = "Location"
elif "location" in headers:
header = "location"
else:
return None

return headers[header].split("/")[-1].strip()

def create(self, unique_id_field=None, name=None, **factory_kwargs):
# Pass in a `unique_id_field` name (e.g. 'schoolId') to have that
# attribute returned alongside the resource_id. Useful if you need
Expand Down Expand Up @@ -146,7 +157,8 @@ def create(self, unique_id_field=None, name=None, **factory_kwargs):
return None, None
return None
self.log_response(response)
resource_id = response.headers["Location"].split("/")[-1].strip()

resource_id = self._get_resource_id_from_location(response.headers)
if unique_id is not None:
return resource_id, unique_id
return resource_id
Expand All @@ -164,8 +176,14 @@ def update(self, resource_id, **update_kwargs):
if self.is_not_expected_result(response, [200, 204]):
return
self.log_response(response)
new_id = response.headers["Location"].split("/")[-1].strip()
assert new_id == resource_id

new_id = self._get_resource_id_from_location(response.headers)

# The ODS/API returns a Location header on PUT requests, but this is not a required feature
# of an Ed-Fi API. Therefore only apply this test if the location header was found
if new_id is not None:
assert new_id == resource_id

return resource_id

def delete_item(self, resource_id):
Expand Down Expand Up @@ -246,7 +264,7 @@ def create_using_dependencies(self, dependency_reference=None, **kwargs):
"attributes": resource_attrs,
}

def delete_with_dependencies(self, reference, **kwargs):
def delete_with_dependencies(self, reference):
"""
Atomically delete an instance of this resource along with all
dependencies. The `reference` parameter will contain the necessary
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class EducationOrganizationInterventionPrescriptionAssociationClient(EdFiAPIClie
}

def create_with_dependencies(self, **kwargs):
rx_reference = self.prescription_client.create_with_dependencies()
rx_reference = self.prescription_client.create_with_dependencies() # type: ignore

return self.create_using_dependencies(
rx_reference,
Expand All @@ -54,7 +54,7 @@ class EducationOrganizationNetworkAssociationClient(EdFiAPIClient):
}

def create_with_dependencies(self, **kwargs):
network_reference = self.network_client.create_with_dependencies()
network_reference = self.network_client.create_with_dependencies() # type: ignore

return self.create_using_dependencies(
network_reference,
Expand All @@ -75,7 +75,7 @@ class EducationOrganizationPeerAssociationClient(EdFiAPIClient):
}

def create_with_dependencies(self, **kwargs):
school_reference = self.school_client.create_with_dependencies()
school_reference = self.school_client.create_with_dependencies() # type: ignore

return self.create_using_dependencies(
school_reference,
Expand Down Expand Up @@ -105,7 +105,7 @@ class LocalEducationAgencyClient(EdFiAPIClient):
}

def create_with_dependencies(self, **kwargs):
service_center_reference = self.service_center_client.create_with_dependencies()
service_center_reference = self.service_center_client.create_with_dependencies() # type: ignore

return self.create_using_dependencies(
service_center_reference,
Expand Down Expand Up @@ -144,7 +144,7 @@ class FeederSchoolAssociationClient(EdFiAPIClient):
}

def create_with_dependencies(self, **kwargs):
feeder_school_reference = self.school_client.create_with_dependencies()
feeder_school_reference = self.school_client.create_with_dependencies() # type: ignore

return self.create_using_dependencies(
feeder_school_reference,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def create_with_dependencies(self, **kwargs):
school_id = kwargs.pop("schoolId", SchoolClient.shared_elementary_school_id())
course_code = kwargs.pop("courseCode", "ELA-01")

assoc_reference = self.section_assoc_client.create_with_dependencies(
assoc_reference = self.section_assoc_client.create_with_dependencies( # type: ignore
schoolId=school_id, courseCode=course_code
)
grade_period = assoc_reference["dependency_ids"]["section_client"][
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class GradebookEntryClient(EdFiAPIClient):
}

def create_with_dependencies(self, **kwargs):
section_reference = self.section_client.create_with_dependencies()
section_reference = self.section_client.create_with_dependencies() # type: ignore
section_attrs = section_reference["attributes"]

return self.create_using_dependencies(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class GraduationPlanClient(EdFiAPIClient):
}

def create_with_dependencies(self, **kwargs):
school_reference = self.school_client.create_with_dependencies()
school_reference = self.school_client.create_with_dependencies() # type: ignore

return self.create_using_dependencies(
school_reference,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class InterventionClient(EdFiAPIClient):

def create_with_dependencies(self, **kwargs):
# Create intervention prescription
rx_reference = self.prescription_client.create_with_dependencies()
rx_reference = self.prescription_client.create_with_dependencies() # type: ignore

# Create intervention
return self.create_using_dependencies(
Expand All @@ -45,7 +45,7 @@ class InterventionStudyClient(EdFiAPIClient):

def create_with_dependencies(self, **kwargs):
# Create intervention prescription
rx_reference = self.prescription_client.create_with_dependencies()
rx_reference = self.prescription_client.create_with_dependencies() # type: ignore

# Create intervention
return self.create_using_dependencies(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ def create_with_dependencies(self, **kwargs):
parent_id = self.create(**parent_attrs)

# Create enrolled student
student_reference = self.student_client.create_with_dependencies(
student_reference = self.student_client.create_with_dependencies( # type: ignore
schoolId=school_id
)

# Associate parent with student to allow updates
assoc_id = self.parent_assoc_client.create(
assoc_id = self.parent_assoc_client.create( # type: ignore
parentReference__parentUniqueId=parent_unique_id,
studentReference__studentUniqueId=student_reference["attributes"][
"studentUniqueId"
Expand All @@ -50,8 +50,8 @@ def create_with_dependencies(self, **kwargs):
}

def delete_with_dependencies(self, reference, **kwargs):
self.parent_assoc_client.delete_item(reference["dependency_ids"]["assoc_id"])
self.student_client.delete_with_dependencies(
self.parent_assoc_client.delete_item(reference["dependency_ids"]["assoc_id"]) # type: ignore
self.student_client.delete_with_dependencies( # type: ignore
reference["dependency_ids"]["student_reference"]
)
self.delete_item(reference["resource_id"])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def create_with_dependencies(self, **kwargs):
studentUniqueId = kwargs.pop("studentUniqueId", StudentClient.shared_student_id())

# Create new student for association
institution_reference = self.institution_client.create_with_dependencies()
institution_reference = self.institution_client.create_with_dependencies() # type: ignore

return self.create_using_dependencies(
institution_reference,
Expand Down
Loading