You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

sedna-controller-enhancement.md 28 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. - [Goals](#goals)
  2. - [Proposal](#proposal)
  3. - [Scope](#scope)
  4. - [Content](#content)
  5. - [Target Users](#target-users)
  6. - [Design Details](#design-details)
  7. - [Garbage Collection](#garbage-collection)
  8. - [Cascade Deletion in Kubernetes](#cascade-deletion-in-kubernetes)
  9. - [Sedna Controller Garbage Collection](#sedna-controller-garbage-collection)
  10. - [Setting Owner References in Joint Inference Controller](#setting-owner-references-in-joint-inference-controller)
  11. - [Pod Reconstruction](#pod-reconstruction)
  12. - [K8S Informer Mechanism](#k8s-informer-mechanism)
  13. - [Informer Workflow](#informer-workflow)
  14. - [Informer Workflow for Joint Inference and Federated Learning Controllers](#informer-workflow-for-joint-inference-and-federated-learning-controllers)
  15. - [Design of the Pod Reconstruction Solution for Federated Learning](#design-of-the-pod-reconstruction-solution-for-federated-learning)
  16. - [Code Logic](#code-logic)
  17. - [Design of the Pod Reconstruction Solution for Joint Inference](#design-of-the-pod-reconstruction-solution-for-joint-inference)
  18. - [Federated learning modifies the CRD to update the pod](#federated-learning-modifies-the-crd-to-update-the-pod)
  19. - [Code Logic](#code-logic-1)
  20. - [Joint Inference modifies the CRD to update the pod](#joint-inference-modifies-the-crd-to-update-the-pod)
  21. - [Code Logic](#code-logic-2)
  22. - [Testing](#testing)
  23. - [Federated Learning Unit Tests](#federated-learning-unit-tests)
  24. - [`Test_deletePod()`](#test_deletepod)
  25. - [Delete the existing pod and recreate it](#delete-the-existing-pod-and-recreate-it)
  26. - [Delete non-existent pods](#delete-non-existent-pods)
  27. - [`Test_updateJob()`](#test_updatejob)
  28. - [Joint Inference Unit Tests](#joint-inference-unit-tests)
  29. - [`Test_UpdateService()`](#test_updateservice)
  30. Sedna leverages KubeEdge's edge-cloud collaboration capabilities to enable cross-edge-cloud collaborative training and inference. Joint inference and federated learning are essential applications of Sedna's edge-cloud collaborative inference. However, the current Sedna project has several functional deficiencies in its joint inference and federated learning controllers, primarily reflected in the following aspects:
  31. 1. After creating a joint inference or federated learning task, the generated cloud and edge task instances do not automatically rebuild after being manually deleted, indicating a lack of self-healing capabilities.
  32. 2. When a joint inference task is deleted, its generated task instances and service configurations are not cascade-deleted, leading to subsequent joint inference task creation failures.
  33. This proposal aims to optimize the joint inference and federated learning controllers to improve task reconstruction and cascade deletion, thereby enhancing the controllers' functionality and application efficiency.
  34. # Goals
  35. The goals of the optimization of Sedna's joint inference and federated learning controllers optimization are to:
  36. 1. Implement cascade deletion for joint inference and federated learning controllers to ensure that task instances and service configurations are synchronously deleted when the tasks are removed, preventing conflicts in subsequent task creation.
  37. 2. Enhance the self-healing capabilities of joint inference and federated learning tasks to ensure that task instances are automatically rebuilt and resume normal operation after failure or manual deletion.
  38. 3. Modify the Joint Inference and Federated Learning CRD to enable automatic updates of pod configurations.
  39. # Proposal
  40. ## Scope
  41. The optimization of the Sedna Joint Inference and Federated Learning controllers only modifies the Joint Inference and Federated Learning controller code. The controllers are decoupled from each other, and the functions that were changed have been updated in the affected controllers as well. Since no changes were made to the CRD, the deployment of existing applications will not be impacted.
  42. The main changes are in the files `/pkg/globalmanager/controllers/jointinference/jointinferenceservice.go` and `/pkg/globalmanager/controllers/federatedlearning/federatedlearningjob.go`.
  43. Additionally, in the file `/pkg/globalmanager/runtime/worker.go`, to meet the need for creating a `deployment` in joint inference, we modified the `CreateDeploymentWithTemplate` function and the `injectDeploymentParam`. The necessary updates were also made in the files`/pkg/globalmanager/controllers/objectsearch/objectsearchservice.go` and `/pkg/globalmanager/controllers/featureextraction/featureextractionservice.go` where these functions are used. We also added a new function, `UpdateDeploymentWithTemplate`, to handle deployment updates.
  44. The test files are `/pkg/globalmanager/controllers/jointinference/jointinferenceservice_test.go` and `/pkg/globalmanager/controllers/federatedlearning/federatedlearningjob_test.go`.
  45. The test files introduced the `fake` package, which brought in some additional dependency code, increasing the pull request size.
  46. ## Content
  47. This project focuses on optimizing the logic of joint inference and federated learning task controllers, with no impact on other modules.
  48. 1. **Cascade Deletion of Joint Inference and Federated Learning Tasks**
  49. - Analyze the current deletion mechanism of custom resources (CRs) and leverage Kubernetes' cascade deletion mechanism to implement cascade deletion for CRs.
  50. - Ensure that task instances and service configurations are synchronously deleted when joint inference and federated learning tasks are removed.
  51. - Optimize existing code to ensure the accuracy and reliability of the cascade deletion functionality.
  52. 2. **Self-Healing Capability of Joint Inference and Federated Learning Tasks**
  53. - Analyze the self-healing mechanisms in Kubernetes' ReplicaSet and KubeEdge's edge application controller, and design and implement a task instance self-healing mechanism.
  54. - Ensure that both cloud and edge task instances automatically rebuild and resume normal operation after failure or manual deletion.
  55. - Develop and optimize related code to ensure the stability of the self-healing functionality.
  56. 3. **Testing and Verification**
  57. - Supplement unit tests and end-to-end (e2e) tests to ensure the optimized controllers operate correctly.
  58. - Demonstrate functionality using existing joint inference and federated learning demos.
  59. 4. **Documentation Updates**
  60. - Update application instance demonstration documentation to describe the reasons for modifications and the usage methods of the optimized cases.
  61. ## Target Users
  62. Users of Sedna's joint inference and federated learning applications.
  63. # Design Details
  64. ## Garbage Collection
  65. ### Cascade Deletion in Kubernetes
  66. In Kubernetes, the Owner Reference provides information about the relationships between objects, allowing the control plane and other API clients to clean up associated resources when an object is deleted. The garbage collector can implement cascade deletion functionality based on this relationship.
  67. Each resource's metadata includes an `ownerReferences` field, an array indicating its owners. When an owner resource is deleted, it is removed from this array, and the garbage collector will reclaim the resource once all owners are deleted.
  68. ### Sedna Controller Garbage Collection
  69. The Owner Reference relationships for JointInferenceService is depicted in the following diagrams:
  70. <img src="./images/ji-ownerreference.png" width="700">
  71. The Owner Reference relationships for FederatedLearningJob is depicted in the following diagrams:
  72. <img src="./images/fl-ownerreference.png" width="700">
  73. #### Setting Owner References in Joint Inference Controller
  74. Taking the Joint Inference Controller as an example, the Owner Reference setting process involves several steps:
  75. <img src="./images/ji-setownerreference.png" width="700">
  76. In `pkg/globalmanager/controllers/jointinference/jointinferenceservice.go`, the controller name and CR kind name are defined:
  77. ```go
  78. // Name is this controller name
  79. Name = "JointInference"
  80. // KindName is the kind name of CR this controller controls
  81. KindName = "JointInferenceService"
  82. ```
  83. The GroupVersionKind for the controller type is defined as follows:
  84. ```go
  85. // Kind contains the schema.GroupVersionKind for this controller type.
  86. var Kind = sednav1.SchemeGroupVersion.WithKind(Name)
  87. ```
  88. The `run` function starts the controller, defines the number of workers, and sets up worker threads:
  89. ```go
  90. for i := 0; i < workers; i++ { go wait.Until(c.worker, time.Second, stopCh) }
  91. ```
  92. In the `worker` function, tasks are dequeued, processed, and marked as done:
  93. ```go
  94. // worker runs a worker thread that just dequeues items, processes them, and marks them done.
  95. // It enforces that the sync is never invoked concurrently with the same key.
  96. func (c *Controller) worker() {
  97. for c.processNextWorkItem() {
  98. }
  99. }
  100. ```
  101. The `sync` function parses the key to get the namespace and name:
  102. ```go
  103. ns, name, err := cache.SplitMetaNamespaceKey(key)
  104. if err != nil {
  105. return false, err
  106. }
  107. if len(ns) == 0 || len(name) == 0 {
  108. return false, fmt.Errorf("invalid jointinference service key %q: either namespace or name is missing", key)
  109. }
  110. ```
  111. It then retrieves the JointInferenceService object from the lister:
  112. ```go
  113. sharedService, err := c.serviceLister.JointInferenceServices(ns).Get(name)
  114. if err != nil {
  115. if errors.IsNotFound(err) {
  116. klog.V(4).Infof("JointInferenceService has been deleted: %v", key)
  117. return true, nil
  118. }
  119. return false, err
  120. }
  121. service := *sharedService
  122. ```
  123. The GroupVersionKind is set:
  124. ```go
  125. service.SetGroupVersionKind(Kind)
  126. ```
  127. A selector is generated, and associated pods are listed:
  128. ```go
  129. selector, _ := runtime.GenerateSelector(&service)
  130. pods, err := c.podStore.Pods(service.Namespace).List(selector)
  131. if err != nil {
  132. return false, err
  133. }
  134. klog.V(4).Infof("list jointinference service %v/%v, %v pods: %v", service.Namespace, service.Name, len(pods), pods)
  135. ```
  136. When no worker failures occur, and if no associated pods exist, the `createWorkers` function is called to create pods:
  137. ```go
  138. else {
  139. if len(pods) == 0 {
  140. active, manageServiceErr = c.createWorkers(&service)
  141. }
  142. ```
  143. In the `createWorkers` function, `createCloudWorker` and `createEdgeWorker` are called to create cloud and edge worker pods. The `runtime.CreatePodWithTemplate` function creates pods, with the OwnerReference set as follows:
  144. ```go
  145. if controllerRef != nil {
  146. pod.OwnerReferences = append(pod.OwnerReferences, *controllerRef)
  147. }
  148. ```
  149. ## Pod Reconstruction
  150. The automatic reconstruction of a failed Pod is determined by the RestartPolicy. In the JointInferenceService, the RestartPolicy is not explicitly set, so the default is always. During the joint inference task, if an issue arises, such as EdgeMesh being improperly configured, preventing the edge from accessing the cloud's port 5000 for cloud-based inference, the edge pod will continuously restart. In the FederatedLearningJob, the RestartPolicy is set to OnFailure.
  151. ### K8S Informer Mechanism
  152. Pod Reconstruction
  153. The automatic reconstruction of a failed Pod is determined by the RestartPolicy. In the JointInferenceService, the RestartPolicy is not explicitly set, so the default is always. During the joint inference task, if an issue arises, such as EdgeMesh being improperly configured, preventing the edge from accessing the cloud's port 5000 for cloud-based inference, the edge pod will continuously restart. In the FederatedLearningJob, the RestartPolicy is set to OnFailure.
  154. K8S Informer Mechanism
  155. Kubernetes uses an Informer instead of a Controller to access the API Server. All Controller operations interact with the Informer, which does not access the API Server each time. Informer uses a ListAndWatch mechanism. When the Informer starts for the first time, it calls the LIST API to retrieve the latest versions of all resource objects and then uses the WATCH API to monitor changes to these objects. This event information is maintained in a read-only cache queue to improve query efficiency and reduce the load on the API Server.
  156. <img src="./images/k8s-informer.png" width=600>
  157. According to the diagram, let's explain the roles of some components in the Informer:
  158. Controller: The core of the Informer, it can create reflectors and control the processLoop. The processLoop pops data from the DeltaFIFO queue, first calls the Indexer for caching and indexing, and then distributes it to the processor for handling.
  159. Reflector: The Informer does not directly access the k8s API server but uses a Reflector to do so. The Reflector monitors specific Kubernetes resources through ListAndWatch. When a resource changes, such as an Added event, the resource object is stored in the local DeltaFIFO cache.
  160. DeltaFIFO: A first-in-first-out cache queue used to store various events returned by the Watch API, such as Added, Updated, and Deleted.
  161. LocalStore: The cache of the Informer, which stores objects from the API server (some of which may still be in DeltaFIFO). Users query the cache directly, reducing the API server's load. LocalStore is accessed by the Lister's List/Get methods.
  162. WorkQueue: After DeltaFIFO receives an event, it stores the event in its data structure, directly operates the data stored in the Store, updates the Store, and then pops the event into the WorkQueue. The Controller triggers the corresponding callback function based on the event type received in the WorkQueue.
  163. #### Informer Workflow
  164. - Informer first lists/watches the API server. The Reflector handles the connection, listing all instances of the resource and monitoring changes through the watch method. If the connection is interrupted, it resumes from the latest resourceVersion. When instances are created, deleted, or updated, the Reflector receives event notifications, which are placed into DeltaFIFO.
  165. - Informer reads these deltas from DeltaFIFO, determines the event type, and updates the local cache accordingly.
  166. - For Added events, the Informer saves the object to the local cache via the Indexer and creates an index. For Deleted events, it removes the object from the cache.
  167. - DeltaFIFO then pops the event into the Controller, which calls the ResourceEventHandler callback for handling.
  168. - ResourceEventHandler filters and places the relevant objects into the WorkQueue.
  169. - The Controller retrieves objects from the WorkQueue, launches a worker, and executes business logic. This typically involves comparing the cluster's current state with the desired state and instructing the API server to make adjustments, like creating new pods for a deployment.
  170. - Workers can use the Lister to get resources from the cache, avoiding frequent access to the API server.
  171. There are three types of `ResourceEventHandler` functions in Informer:
  172. ```go
  173. // ResourceEventHandlerFuncs is an adaptor to let you easily specify as many or
  174. // as few of the notification functions as you want while still implementing
  175. // ResourceEventHandler.
  176. type ResourceEventHandlerFuncs struct {
  177. AddFunc func(obj interface{})
  178. UpdateFunc func(oldObj, newObj interface{})
  179. DeleteFunc func(obj interface{})
  180. }
  181. ```
  182. The logic for these three functions is user-defined. After registering the `ResourceEventHandler` during controller initialization, the corresponding `ResourceEventHandler` will be triggered whenever an instance of the object is created, deleted, or updated.
  183. ### Informer Workflow for Joint Inference and Federated Learning Controllers
  184. <img src="./images/k8s-controller.png" width=950>
  185. Taking `jointinferenceservice.go` as an example, the `New()` function creates a new `JointInferenceService` controller to keep the relevant pods in sync with the corresponding `JointInferenceService` objects. In the `New()` function, the `informer` is initialized.
  186. ```go
  187. podInformer := cc.KubeInformerFactory.Core().V1().Pods()
  188. serviceInformer := cc.SednaInformerFactory.Sedna().V1alpha1().JointInferenceServices()
  189. ```
  190. The service informer uses a custom handler:
  191. ```go
  192.     serviceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
  193.         AddFunc: func(obj interface{}) {
  194.             jc.enqueueController(obj, true)
  195.             jc.syncToEdge(watch.Added, obj)
  196.         },
  197.         UpdateFunc: func(old, cur interface{}) {
  198.             jc.enqueueController(cur, true)
  199.             jc.syncToEdge(watch.Added, cur)
  200.         },
  201.         DeleteFunc: func(obj interface{}) {
  202.             jc.enqueueController(obj, true)
  203.             jc.syncToEdge(watch.Deleted, obj)
  204.         },
  205.     })
  206. ```
  207. The pod informer uses a custom handler:
  208. ```go
  209.     podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
  210.         AddFunc:    jc.addPod,
  211.         UpdateFunc: jc.updatePod,
  212.         DeleteFunc: jc.deletePod,
  213.     })
  214. ```
  215. The `EventHandler` functions (`addPod`, `updatePod`, `deletePod`) here simply add the relevant objects to the queue without performing any other processing.
  216. `podInformer.Lister()` creates a `Lister()` to retrieve Pod resources, and `podInformer.Informer().HasSynced` checks whether the Informer's cache has been synchronized.
  217. ```go
  218.     jc.serviceLister = serviceInformer.Lister()
  219.     jc.serviceStoreSynced = serviceInformer.Informer().HasSynced
  220. //...
  221.     jc.podStore = podInformer.Lister()
  222.     jc.podStoreSynced = podInformer.Informer().HasSynced
  223. ```
  224. The process of synchronizing resources from the API server is carried out by the jointinferenceservice controller in the Run() function. The Run() function starts the main goroutine responsible for the watch and sync services.
  225. ```go
  226.     if !cache.WaitForNamedCacheSync(Name, stopCh, c.podStoreSynced, c.serviceStoreSynced) {
  227.         klog.Errorf("failed to wait for %s caches to sync", Name)
  228.         return
  229.     }
  230. ```
  231. After starting the Informer, it waits for the local cache to sync before launching the workers. When a change event is received, the changed object is retrieved from the event, an object key (in the form of namespace/name) is generated, and the key is placed into the worker queue. The `worker()` function then calls the `c.processNextWorkItem()` function.
  232. ```go
  233. func (c *Controller) worker() {
  234.     for c.processNextWorkItem() {
  235.     }
  236. }
  237. ```
  238. The `processNextWorkItem` function retrieves the key from the worker queue and calls `sync()`. In the `sync()` function, it fetches the actual object from the local cache using the lister and performs the relevant synchronization operations.
  239. ### Design of the Pod Reconstruction Solution for Federated Learning
  240. <img src="./images/fl-recreate-solution.png" width=800>
  241. Listen for delete events. When the Informer detects a delete event with `OwnerReference` set to `FederatedLearning`, it triggers the `DeletePod` function, which reconstructs the pod. The newly created pod will be almost identical to the original pod, retaining its configuration and specifications, but its resource version and UID will be reset to allow for regeneration.
  242. #### Code Logic
  243. - Pre-Reconstruction Check
  244. - Check if the pod is owned by a `FederatedLearningJob`.
  245. - Check if the pod has already been recreated: use `c.recreatedPods.Load(pod.Name)` to verify if this pod has already been recreated. If it has been recreated, do not attempt to recreate it again.
  246. - Recreate Pods
  247. - Use `pod.DeepCopy()` to create a deep copy of the pod.
  248. - Reset certain unique identifiers (such as `ResourceVersion` and `UID`) and status fields.
  249. - Call the Kubernetes API to create a new pod using `c.kubeClient.CoreV1().Pods(pod.Namespace).Create`.
  250. - If the creation is successful, log the event and mark the pod as recreated.
  251. ```go
  252.     // Create a deep copy of the old pod
  253.     newPod := pod.DeepCopy()
  254.     // Reset the resource version and UID as they are unique to each object
  255.     newPod.ResourceVersion = ""
  256.     newPod.UID = ""
  257.     // Clear the status
  258.     newPod.Status = v1.PodStatus{}
  259.     // Remove the deletion timestamp
  260.     newPod.DeletionTimestamp = nil
  261.     // Remove the deletion grace period seconds
  262.     newPod.DeletionGracePeriodSeconds = nil
  263.     _, err := c.kubeClient.CoreV1().Pods(pod.Namespace).Create(context.TODO(), newPod, metav1.CreateOptions{})
  264.     if err != nil {
  265.         return
  266.     }
  267. ```
  268. - Mark as Recreated and Set Timer to Clean Up Record:
  269. - Use `c.recreatedPods.Store(pod.Name, true)` to mark that this pod has been recreated.
  270. - Set a timer to clear this mark after 5 seconds (`c.recreatedPods.Delete(pod.Name)`), allowing the reconstruction logic to be triggered again if the pod is deleted once more.
  271. ```go
  272.     // mark the pod as recreated
  273.     c.recreatedPods.Store(newPod.Name, true)
  274.     // set a timer to delete the record from the map after a while
  275.     go func() {
  276.         time.Sleep(5 * time.Second)
  277.         c.recreatedPods.Delete(pod.Name)
  278.     }()
  279. ```
  280. We add a `sync.Map` type field called `recreatedPods` to the Controller's data structure to prevent a pod from being recreated multiple times during a single delete event. When a pod is manually deleted and successfully recreated, its name is added to `recreatedPods`. If the `deletePod` function is called again during the same delete event, the pod name will already exist in `recreatedPods`, preventing the manually deleted pod from being deleted and recreated again. A timer is also set to clear the mark after 5 seconds, allowing the reconstruction logic to be triggered again if the pod is manually deleted later.
  281. ```go
  282. type Controller struct{
  283. //...
  284. preventRecreation bool
  285. //...
  286. }
  287. ```
  288. ### Design of the Pod Reconstruction Solution for Joint Inference
  289. <img src="./images/ji-deployment-process.png" width=1000>
  290. Inference tasks are stateless workloads, so Kubernetes' native component `Deployment` can be leveraged for pod self-healing. A complete process for monitoring and handling resource changes needs to be built. The `Deployment Informer` can replace the `Pod Informer` to manage `worker` control.
  291. - Obtain the `Deployment Informer` resource from the informer factory.
  292. - Register event handlers (`addDeployment`, `updateDeployment`, `deleteDeployment`) in the informer.
  293. - Start the informer and sync data with the Kubernetes API.
  294. - Before processing events, wait for the local cache to synchronize with the API Server.
  295. - When a `Deployment` resource in the cluster changes, the `Informer` will trigger the corresponding event handler.
  296. In addition, to ensure that the `deployment` resource is not accidentally deleted, we adopt the same method used in Federated Learning for recreating `pods` to recreate the `deployment`.
  297. ### Federated learning modifies the CRD to update the pod
  298. Flowchart for updating pod by modifying the CRD in the Federated Learning controller:
  299. <img src="./images/fl-crdupdate-solution.png" width=800>
  300. Listen for update events: When the Informer detects an update event related to the `FederatedLearningJob`, if there are changes in the CRD, the original pod is deleted, and a new pod is created based on the updated parameters in the CRD.
  301. #### Code Logic
  302. The `updateJob` function is triggered, and it first checks whether an update is necessary.
  303. - If `old` and `cur` cannot be converted into `*sednav1.FederatedLearningJob` objects, it returns immediately.
  304. - If the `ResourceVersion` of `oldJob` and `curJob` are the same, it indicates there are no changes, so the function returns without processing the update.
  305. - Set the `preventRecreation` flag to true to prevent Pod recreation during the update process.
  306. Next, compare the parameters of `oldJob` and `curJob`.
  307. - Compare the `old.Generation` and `cur.Generation` fields. The CRD has a `Generation` field within its `Spec`. This field is automatically generated and increments by 1 every time the CRD object is created or modified, starting at 1 during creation. The `Generation` field only changes when the `Spec` is modified, not when the `Status` is updated. Therefore, this field can be used to determine if a change occurred in the `Spec`. If they are not equal, it means that the parameters of the `FederatedLearningJob` have changed.
  308. - Then, it iterates through the list of Pods and deletes each one.
  309. - Use the updated `curJob.Spec` to recreate the `AggWorker` and `TrainWorker`.
  310. - Finally, reset the `preventRecreation` flag to `false` to allow future Pod self-healing without interference.
  311. ### Joint Inference modifies the CRD to update the pod
  312. Flowchart for updating pod by modifying the CRD in the Joint Inference controller:
  313. <img src="./images/ji-crdupdate-solution.png" width=800>
  314. When an update event is detected, if the Informer detects an update to the `JointInferenceService`, and there are changes to the CRD, the `deployment` will be updated according to the new parameters in the CRD.
  315. #### Code Logic
  316. The logic for updating pods by modifying the CRD in Joint Inference is largely the same as in Federated Learning.
  317. Relevant fields are compared, and if they are not equal, it indicates that the parameters of the `JointInferenceService` have changed. The `cloudWorker` and `edgeWorker` are updated using the new `curService.Spec`.
  318. # Testing
  319. ## Federated Learning Unit Tests
  320. Unit tests are used instead of end-to-end tests, focusing on testing the two modified functions: `deletePod()` and `updateJob()`.
  321. ### `Test_deletePod()`
  322. #### Delete the existing pod and recreate it
  323. - A simulated Kubernetes client is created using `fake.NewSimpleClientset()`.
  324. - A test pod is created through the `fakeclient`.
  325. - A controller is created, and the `fakeclient` is registered with it.
  326. - The test pod is passed to the `controller.deletePod()` function, and `fakeClient.CoreV1().Pods("default").Get(context.TODO(), "test-pod", metav1.GetOptions{})` is used to check if the pod has been recreated; if it has not been recreated, the test fails.
  327. #### Delete non-existent pods
  328. - Create a simulated client.
  329. - Create a controller.
  330. - Call `controller.deletePod()` to delete a non-existent pod.
  331. - Confirm whether an error occurs.
  332. ### `Test_updateJob()`
  333. - Mock the pod list process.
  334. - Create a simulated client.
  335. - Create datasets, models, and relevant job and pod resources.
  336. - Initialize the controller, passing in the simulated client, test jobs, mocked pod list, and other necessary dependencies.
  337. - Define a new job and update some of its parameters (changing the `batch_size` in `TrainingWorker` from `32` to `16`).
  338. - Call the `updateJob` function to update the old job to the new job, simulating the job update process in a real environment.
  339. - Validate the update results; if the parameters match the expectations after the update, the test passes.
  340. ## Joint Inference Unit Tests
  341. ### `Test_UpdateService()`
  342. - Create simulated clients for Sedna and Kubernetes.
  343. - Create an old service that includes configuration information for the cloud worker and edge worker, as well as two model resources.
  344. - Create deployment and pod resources based on the old service.
  345. - Initialize the controller, setting it up with the simulated client, pod list, and deployment list.
  346. - The controller's `sendToEdgeFunc` is set to an empty function (not performing actual edge communication).
  347. - A copy of the old joint inference service is made, modifying the hard example mining parameter in the edge worker from `value1` to `value2`, while also incrementing the service's `Generation` value.
  348. - Call the controller's `updateService` function to trigger an update to the joint inference service.
  349. - The test verifies whether the updated joint inference service can be successfully retrieved through the simulated client.
  350. - Check whether the updated HEM parameter has been correctly changed from `value1` to `value2`, ensuring that the service update logic executes correctly.