Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Capabilities v2 #357

Merged
merged 52 commits into from Jun 30, 2020
Merged

Capabilities v2 #357

merged 52 commits into from Jun 30, 2020

Conversation

eguzki
Copy link
Member

@eguzki eguzki commented Apr 23, 2020

Reconcile cluster k8s resources with 3scale using the 3scale API.

Include APIaaP objects

Some considerations for the implementation:

  • Some types have owner and cannot be shared. For instance Products. Product belong to one account provider and cannot be shared between account providers. Same for limits. Limits are owned by some application plan and cannot be shared by multiple application plans.
  • Objects can be invalid. Invalid objects will be marked as such, updated status with error message and will be filtered out by operator controllers. User should delete them and do not try to create new objects with the same spec. Invalidating an object A will trigger invalidation cascade to all objects with references to the object A. The alternative to invalidate objects is, object removal. Invalidating objects approach lets the user know what happened.
    • For instance, ActiveDocs belonging to a account provider and product, and those account provider and product are not related.
  • Backend usage: 1 product cannot be using same backend multiple times. Unique key is composition of <product_id, backend_id>.
    • Proposed solution: In the spec of the product, there is a map called backend usages with the key being backend_id and the value being the backend usage object reference. This way it is ensured 1 product not using same backend multiple times.
  • Deletion cascade: Implement using ownerreferences? Ownerreferences does not work for Applicationa. Application object has two owners, application plan and account. If any of those owners are deleted, application should be deleted as well (orphan object). K8s garbage collector works only when ALL the owners have been deleted. Hibrid solution? ownerreferences for simple relations, operator based deletetion cascade for complex relations like the applications?
  • Orphan objects: when some controller finds an orphan object (some referenced object does not exist), it will wait for the referenced object to exist. The controller will start retry with exponential back-off pattern until the referenced object is found (which could never happen). This way it is allowed temporary inconsistency, like for instance create product resource before backend resource. Product object will be ofphan until backend is found.
  • Objects lifecycle
    • Based on status.Conditions k8s common pattern
    • states:
      • InSync: Sync process going on
      • Orphan: Temporarily invalid. Waiting for some resource to exist.
      • Synced: Sync'ed
      • Invalid: Invalid object. Spec should be changed.
  • Implement 3scale state cache in status? Avoid too many calls to 3scale API for every reconciliation loop. Assume 3scale state (read from 3scale API endpoints) does not change if 3scale operator does not change it (using 3scale API delete, put, post methods)
  • If Account Provider object is deleted, the products, backends, accounts, activedocs will not be deleted from 3scale. It is just considered "disconnected". Current PoC implements deletion of all services from 3scale when "binding" object is deleted (finalizers are used for that regard)
  • 3scale API does not allow deleting a backend if it is being used by a Product. This constraint is not easy to implement as k8s resources. If the user deletes a backend. All products using that deleted backend will be marked as orphan and will not sync until reference is found or backend usage is deleted.
  • 3scale does not allow updating the ownership of some object types. For instance an application plan owned by a product cannot be changed and owned by another product. This kind of relations will be implemented by defining objects inside owner spec instead of having their own CRD. For instance application plans will not have their own CRD.
    • Detailed explanation: When there is a relation 1:N from A -> B, A instance owns multiple B instances. The owner of those B instance cannot be changed to some other C object. It is required to enforce ownership as fixed, not changeable. The reconciliation logic for the scenario where ownership can be updated is very complex. For example: Product A owns several Metrics and Application plans. One of these plans, plan P, owns a limit, L, related to some metric M. If application plan P's owner is changed to product B the entire system will be inconsistent. As the application plan P would be owned by B and it would own limits with metrics owned by a different product A. This scenario is not allowed in 3scale.
  • Relations 1:N will be implemented using one of the following options, depending on each relationship type:
    • B instance contains a reference to A. This way you make sure B instance is not owned by multiple A's. But this solution has a big weakness. There is nothing like relational integrity, that prevents the user to update the reference hold by B to A and change to a reference to C or even something not existing. Any change has to be validated by the controller. Example: Product 1 -> N ActriveDocs
    • A maintains a list/map of B references. Then 1:N relation has to be validated by the controller making sure B instance is not owned by several A instances. Example: Product 1 -> N Backend (backend usage relationship)
    • A maintaing a list/map of B objects as subresources. B object type will not have their own CRD. Ownership is fixed. Example: Product 1 -> N Application plans
  • 3scale entities are linked to a Provider Account. This provider account will be referenced by the credentials secret to sync with 3scale using REST API. Each object will need provider account to sync. The look up process can be as follows:
    • If provider_account_reference attribute is found, referenced provider account will be used.
    • If no provider_account_reference attribute is found, some secret with hardcoded name will be looked up in the namespace. For example, default_provider_account secret. If found, that one will be used.
    • If no provider_account_reference attribute is found AND provider account default secret in the namespace is not found either, then, 3scale default provider account (3scale-admin) will be looked up using system-seed secret in the current namespace.
    • If nothing is successfully found, the object will be marked as orphan.

Design proposal:

Backend

name: Operated Backend A                                         
privateBaseURL: https://example.com:8443                         
systemName: OperatedBackendA                                     
metrics:
  hits:                                                          
    description: Number of API hits                              
    friendlyName: Hits                                           
    unit: "hit"                                                  
methods:                                                         
  method01:                                                      
    friendlyName: Method01                                       
mappingRules:                                                    
  - httpMethod: GET                                              
    pattern: "/pets"                                             
    metricMethodRef: metric02                                    
  - httpMethod: POST                                             
    pattern: "/pets"                                             
    metricMethodRef: hits 

Product

provider_account_reference: REF
deployment:                                                 
  apicastSelfManaged:                                       
    stagingPublicBaseURL: "https://staging2.example.com"    
    productionPublicBaseURL: "https://prod2.example.com"    
    authentication:                                         
      appKeyAppID:  {}          
metrics:                                                            
  hits:                                   
    description: Number of API hits       
    friendlyName: Hits
    unit: "hit"                           
methods:                                  
  method01:                               
    friendlyName: Method01                
mappingRules:                         
  - httpMethod: GET                   
    pattern: "/pets"                  
    metricMethodRef: method01         
  - httpMethod: GET                   
    pattern: "/pets/id"               
    metricMethodRef: method01
backendUsages:      
  backendA:         
    path: /A        
applicationPlans:                  
  plan01:                          
    name: "My Plan 01"
    limits: 
      - period: month            
        value: 100               
        metricMethodRef:         
          systemName: hits       
      - period: month            
        value: 200               
        metricMethodRef:         
           systemName: hits       
           backend: backendA       
    pricingRules:                  
      - from: 1                    
        to: 100                    
        pricePerUnit: "15.45"      
        metricMethodRef:           
          systemName: hits         
      - from: 1                    
        to: 100                    
        pricePerUnit: "15.45"      
        metricMethodRef:           
          systemName: hits         
          backend: backendA        

Account (NOT INCLUDED IN THIS PR)

provider_account_reference: REF
applications: 
  app_id: 
    attributes: {}

ActiveDocs (NOT INCLUDED IN THIS PR)

provider_account_reference: REF
product_reference: REF
attributes: {}

@eguzki eguzki force-pushed the capabilities-v2 branch 2 times, most recently from 37ea998 to 057161b Compare April 27, 2020 13:19
@eguzki eguzki requested a review from miguelsorianod May 4, 2020 09:15
@eguzki eguzki self-assigned this May 4, 2020
@eguzki eguzki force-pushed the capabilities-v2 branch 2 times, most recently from e30d18f to 74b8bd8 Compare May 14, 2020 18:41
@eguzki eguzki force-pushed the capabilities-v2 branch 2 times, most recently from 6dd2033 to 90976f5 Compare June 9, 2020 16:25
@eguzki eguzki force-pushed the capabilities-v2 branch 3 times, most recently from c5b3550 to 9bea0f7 Compare June 19, 2020 15:30
@eguzki eguzki assigned miguelsorianod and unassigned eguzki Jun 26, 2020
@eguzki eguzki marked this pull request as ready for review June 26, 2020 09:49
@codeclimate
Copy link

codeclimate bot commented Jun 30, 2020

Code Climate has analyzed commit a3c1b52 and detected 309 issues on this pull request.

Here's the issue category breakdown:

Category Count
Complexity 34
Duplication 104
Style 171

View more on Code Climate.

@miguelsorianod miguelsorianod merged commit 79b9e81 into master Jun 30, 2020
@eguzki eguzki deleted the capabilities-v2 branch June 30, 2020 12:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants