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

Extract all data acquisition from ResourceService #1288

Merged
merged 37 commits into from
Dec 11, 2023

Conversation

drewnoakes
Copy link
Member

@drewnoakes drewnoakes commented Dec 8, 2023

Fixes #1114

This creates a new class DcpDataSource that encapsulates all code related to obtaining resource data from DCP in the ResourceService. This cleans the service code up a lot and makes the responsibilities clearer.

Channel<T> for KubernetesObject and ResourceChange objects have been replaced with direct code invocations, skipping queuing. We still have a downstream channel for the subscription that ensures front/back are decoupled.

Added some API docs.

Before

image

After

image

This creates a new class `KubernetesDataSource` that encapsulates all code related to obtaining data from kubernetes in the `ResourceService`. This cleans the service code up a lot and makes the responsibilities clearer.

The use of `Channel<ResourceChange>` has been removed. Instead, code invocations are made directly, skipping queuing. We still have two other channels that ensure front/back are decoupled. The kubernetes channel will ultimately also be removed, leaving just a channel for the subscriber.

Added some API docs.
This is the second removal of a `Channel<T>` from this component. Now, updates are passed by direct code invocations, skipping queuing. We still have a downstream channel for the subscriber to decouple front/back.
A few changes here:

- Use more consistent names throughout. They're not "extra" or "additional" arguments. In fact they are a complete replacement. In their absence, the "spec" environment is used.
- Move creation of docker inspection task to view model method, making the various "handle * update" methods more uniform, for future refactoring.
- Remove special handling in `ProcessKubernetesChange` for one scenario. Simplifies the signature, and the code a fair bit.
We only need to know if the value is "upserted" (updated or inserted), or "deleted".

Recent changes to threading/queuing in the code here changed some timing. The arrival of one kind of resouce can trigger the publication of another, and these would always be "modified", however they could arrive before that resource's stream published that instance. The rest of the code handles these happening out-of-order. We just need to treat add and modified the same way, so they've been merged.
There are multiple kubernetes resource types, each with its own monitoring stream. Across these resources, updates arrive concurrently. The update flows for each type of resource can interact with state stored for other resources. Therefore we use a semaphore to ensure that only one resource update can flow through the system at a time.

Another option would have been to make the collections concurrent, or use explicit locking. Concurrent collections are heavy. Explicit locking is tricky to get right. This mutual exclusion via top-level semaphore seems like a safe and elegant approach for now.
When this method is passed the same collection twice, the `FromSpec` value will always be true, because every item in the list is in the list. Instead we pass `null` and consider all items as from the spec.
Each instance of `WatchKubernetesResource<T>` produces a single type, that would flow through `ProcessKubernetesChange` to look up the relevant handler. Instead, pass the handler in to `WatchKubernetesResource<T>` so it can be invoked directly.
This object is a snapshot of a service's state, not a service itself. We have another class which is a service. Append "Snapshot" to differentiate. Future work will extend this concept of snapshots more broadly.
Note that it's just the collection that becomes immutable here. The elements are still currently mutable. That will change when we split front/back ends, and have a snapshot on the backend with a view model on the front end.
This data is now available from DCP, so we don't have to launch processes to query docker for this data any more, which simplifies things quite nicely.
Now that project snapshots derive from executable snapshots, we can unify a bunch of construction logic.
We still have some types named ViewModel, but they'll be renamed later in the split.
It's not clear what object the name refers to, so make it more specific. It's internal, so we can always rename it again later. This name pairs nicely with `ObjectChange` too.
The methods that handle executable and container updates are largely the same. Extract that commonality to a new method.
Copy link
Member

@tlmii tlmii left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change looks good to me. The diagrams definitely helped set the stage for what was changing, so I apprecaite that. A few questions and comments but nothing major from my perspective.

/// <summary>
/// Immutable snapshot of project state at a point in time.
/// </summary>
public class ProjectViewModel : ExecutableViewModel
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the idea here is that because, under the hood, projects are just executables with a project path, so we should have these viewmodels structured the same way? Since this doesn't get exposed to the user I don't see an issue there I guess, but I worry a bit about having irrelevant (less relevant? not normally relevant?) things like working directory, etc on the ProjectViewModel.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right. From what I can tell, DCP treats projects as regular executables, and we only differentiate based on some metadata that links them to the original project. The more I looked at the code, the more projects "wanted" to be executables. When I introduced that change, the next commits removed a bunch of code and things got a bit tidier from there.

I can't think of a reason not to show working directory and arguments for projects too. Seeing arguments could be helpful, in particular.


return new ExecutableViewModel
{
Name = executable.Metadata.Name,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oof. It'd be nice if there was some way to reuse the initializers between these, but I can't think of one.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Constructors wouldn't much help. We could wrap an executable with a project, but that also feels fairly contrived and has a runtime cost (polymorphism, delegation). This bit of localised duplication seemed like a decent outcome. Open to ideas of course.

}
}

if (_resourceAssociatedServicesMap.TryGetValue((resourceKind, name), out var resourceServiceMappings))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to remember, is the resourceKind part of this mapping because of the original idea that resource names were only guaranteed unique within a particular resource type? But more recently it's been stated that they'll be globally unique. So maybe we could simplify the key for the mapping now?

And by "now" I don't mean this PR since you've already got a lot of changes here. Just making sure someone else reads this thought 😊

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have wondered the same thing. If we go for unique names, we should make a few other changes throughout. It has an impact on #1274 too, as currently we have a composite key (name/kind) in the protocol.

@drewnoakes drewnoakes merged commit 1f56e60 into dotnet:main Dec 11, 2023
8 checks passed
@drewnoakes drewnoakes deleted the resource-data-flow branch December 11, 2023 10:27
@github-actions github-actions bot locked and limited conversation to collaborators Apr 27, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Use EffectiveEnv for ContainerViewModel computation
4 participants