-
Story
-
Resolution: Unresolved
-
Undefined
-
None
-
None
-
None
Implement minimal support for CMMO (Cost Management Metrics Operator) in the Koku Sources API. This includes creating new endpoints, adding source type mappings, supporting CMMO's filter[field] query syntax, and wrapping responses in the expected format. The goal is to allow CMMO to register and query sources via Koku's Sources API without requiring the full sources-api-go service.
Background
CMMO uses numeric string IDs ("1", "2", "3", "4") for source types while Koku uses provider constants ("OCP", "AWS", "Azure", "GCP"). CMMO also expects:
- Specific API path: /api/sources/v1.0/
- Response format: {{"meta":
{"count": N}
, "data": [...]}}
- filter[field] query parameter syntax
- Endpoints for /source_types, /application_types, and /applications
Implementation Tasks
1. Create Source Type Mapping Module
Create a new mapping module to convert between CMMO's numeric string source type IDs and Koku's provider type constants.
Technical Details:
- Create new file: koku/sources/api/source_type_mapping.py
- Define three dictionaries:
- SOURCE_TYPE_ID_TO_TYPE: Maps "1" → "OCP", "2" → "AWS", etc.
- SOURCE_TYPE_TO_ID: Reverse mapping (auto-generated from above)
- SOURCE_TYPE_ID_TO_NAME: Maps "1" → "openshift", "2" → "amazon", etc.
- Use hardcoded values (no configuration needed)
2. Implement /source_types Endpoint
Create endpoint that returns a list of source types with their IDs and names.
Technical Details:
- Endpoint: GET /api/sources/v1.0/source_types
- Support query parameter: filter[name]=openshift
- Response format: {{"meta":
{"count": N}
, "data": [
{"id": "1", "name": "openshift"}, ...]}}
- Return hardcoded list of source types (no database queries needed)
- Source type name must be "openshift" (lowercase), not "ocp"
Files to Create:
- koku/sources/api/source_type_views.py
3. Implement /application_types Endpoint
Create endpoint that returns application types. Since CMMO only supports Cost Management application (hardcoded as ID "0"), this endpoint returns a single hardcoded entry.
Technical Details:
- Endpoint: GET /api/sources/v1.0/application_types
- Support query parameter: filter[name]=/insights/platform/cost-management
- Response format: {{"meta":
{"count": 1}
, "data": [
{"id": "0", "name": "/insights/platform/cost-management"}]}}
Files to Create:
- koku/sources/api/application_type_views.py
4. Implement /applications Endpoint
Create endpoint for managing application associations. Since application_type_id is always "0" (Cost Management, hardcoded in CMMO), no database storage is needed.
Technical Details:
- Endpoint: GET /api/sources/v1.0/applications - List applications
- Endpoint: POST /api/sources/v1.0/applications - Create application association
- Support query parameters: filter[source_id], filter[application_type_id]
- Response format: {{"meta":
{"count": N}
, "data": [
{"id": "...", "source_id": "...", "application_type_id": "0"}, ...]}}
- For GET: Return applications with hardcoded application_type_id="0" for all sources
- For POST: Validate that application_type_id is "0", then return success (no database storage)
Files to Create:
- koku/sources/api/application_views.py
5. Add /api/sources/v1.0/ Route Alias
Add a route alias to support CMMO's expected API path format while maintaining backward compatibility.
Technical Details:
- Existing path: {{
{API_PATH_PREFIX}v1/}} (e.g., /api/v1/sources/)
* New CMMO path: /api/sources/v1.0/
* Both paths should route to the same viewset
Files to Modify:
* koku/sources/urls.py
* koku/sources/api/urls.py (add routes for new endpoints)
----
h3. 6. Override SourceFilter to Support filter[field] Syntax
Extend SourceFilter to support CMMO's filter[field] query parameter syntax while maintaining backward compatibility. Add new filters for source_type_id and source_ref.
Technical Details:
* django-filter does NOT natively support filter[field] syntax
* Override filter_queryset to manually parse filter[field] parameters using querystring_parser
* Support both filter[field] and direct field syntax (backward compatible)
* Add source_type_id filter that maps CMMO ID to Koku provider type
* Add source_ref filter that maps to authentication_credentials_cluster_id JSON field lookup
Files to Modify:
* koku/sources/api/view.py (SourceFilter class)
----
h3. 7. Modify AdminSourcesSerializer to Accept source_type_id in POST
Update the serializer to accept source_type_id (CMMO format) in POST requests and convert it to source_type (Koku format) for database storage.
Technical Details:
* Map source_type_id → source_type using SOURCE_TYPE_ID_TO_TYPE
* Accept both source_type and source_type_id (backward compatible)
* Handle source_ref by storing in authentication.credentials.cluster_id for OCP sources
Files to Modify:
* koku/sources/api/serializers.py (AdminSourcesSerializer class)
----
h3. 8. Add source_type_id and source_ref to SourcesSerializer Responses
Update the serializer to include source_type_id and source_ref fields in GET responses.
Technical Details:
* source_type_id: Convert Koku's source_type ("OCP") to CMMO's numeric ID ("1")
* source_ref: Extract from authentication.credentials.cluster_id JSON field for OCP sources
* Both fields should be read-only (SerializerMethodField)
Files to Modify:
* koku/sources/api/serializers.py (SourcesSerializer class)
----
h3. 9. Wrap List Responses with meta.count Format
Update the SourcesViewSet to wrap all list responses in CMMO's expected format.
Technical Details:
* CMMO expects responses in format: "meta": {"count": N}, "data": [...]
* Override SourcesViewSet.list() method
* Wrap response data in expected format
* Apply to all list endpoints (sources list, filtered results, etc.)
Files to Modify:
* koku/sources/api/view.py (SourcesViewSet class)
----
h2. Files Summary
Files to Create:
* koku/sources/api/source_type_mapping.py
* koku/sources/api/source_type_views.py
* koku/sources/api/application_type_views.py
* koku/sources/api/application_views.py
Files to Modify:
* koku/sources/urls.py
* koku/sources/api/urls.py
* koku/sources/api/view.py
* koku/sources/api/serializers.py
----
h2. Acceptance Criteria
h3. Source Type Mapping
* ( ) File koku/sources/api/source_type_mapping.py exists with all mappings
* ( ) SOURCE_TYPE_ID_TO_TYPE correctly maps all 4 provider types ("1"→"OCP", "2"→"AWS", "3"→"Azure", "4"→"GCP")
* ( ) SOURCE_TYPE_TO_ID correctly reverse maps all provider types
* ( ) SOURCE_TYPE_ID_TO_NAME includes "openshift" (lowercase) for ID "1"
h3. /source_types Endpoint
* ( ) GET /api/sources/v1.0/source_types returns list of source types
* ( ) Response includes meta.count and data array
* ( ) Each source type has id (string) and name (string) fields
* ( ) filter[name]=openshift returns only OpenShift source type
* ( ) All 4 provider types are included
h3. /application_types Endpoint
* ( ) GET /api/sources/v1.0/application_types returns single application type
* ( ) Response includes meta.count (value: 1) and data array
* ( ) Application type has id: "0" and name: "/insights/platform/cost-management"
* ( ) filter[name]=/insights/platform/cost-management returns the entry
h3. /applications Endpoint
* ( ) GET /api/sources/v1.0/applications returns list of applications
* ( ) GET /api/sources/v1.0/applications?filter[source_id]=X filters by source_id
* ( ) Each application has id, source_id, and application_type_id: "0"
* ( ) POST /api/sources/v1.0/applications accepts source_id and application_type_id
* ( ) POST validates application_type_id is "0" (reject others)
h3. Route Alias
* ( ) Route /api/sources/v1.0/ is accessible
* ( ) Existing route {{{API_PATH_PREFIX}v1/}} still works
- ( ) Both routes point to the same endpoints
Filter Support
- ( ) filter[source_type_id]=1 correctly filters sources by provider type
- ( ) filter[source_ref]=uuid correctly filters sources by cluster_id
- ( ) Existing filters (name, type) still work as direct query parameters
- ( ) Both filter[field] and direct field syntax work (backward compatible)
Serializer Updates
- ( ) POST request with source_type_id: "1" creates source with source_type: "OCP"
- ( ) POST request with source_type: "OCP" still works (backward compatible)
- ( ) POST request with source_ref: "uuid" stores value in authentication.credentials.cluster_id for OCP sources
- ( ) GET responses include source_type_id (correct numeric string based on provider type)
- ( ) GET responses include source_ref (populated from authentication.credentials.cluster_id for OCP sources, null otherwise)
Response Format
- ( ) GET /api/sources/v1.0/sources/ returns {{"meta":
{"count": N}
, "data": [...]}}
- ( ) meta.count reflects the actual number of items in data array
- ( ) Empty results return {{"meta":
{"count": 0}
, "data": []}}
Testing Recommendations
- Unit tests for mapping module
- Integration tests for each new endpoint
- Test backward compatibility for existing API consumers
- Test filter syntax (both old and new formats)
- Test serializer validation and response format
Performance Considerations
- JSON field filtering (authentication_credentials_cluster_id) is slower than indexed columns
- Consider adding database index on JSON path if performance becomes an issue
- Current approach is acceptable for minimal implementation