---
# System prepended metadata

title: Design changes based on review

---

# Design changes based on review

## Design keypoint
### From review decision
- CAPI is responsible for orchestrate machine update
- Split changes and allow multiple external updaters to collaborate
- InPlaceUpdatePlan CR is introduced to make update blueprint and then execute
- leverage existing CR (KubeadmControlPlane/MachineDeployment/MachineSet/Machine) to handle in-place update

### From my idea
- CAPI leverge updaterUpdateTask contract to exchange status
  - implement webhook is complex. CR contract is a widely used techinque for commmuncating between capi core and capi providers.
- Define aborting updatePhase
  - for inplace update, we need handle abort carefully to avoid machine run into chaos status
  - when a new change comes while doing inplace update, we shall call `abort` to abort current on-going inplace update and wait unitl it completed.
   


## Chanlleges
- How to identify the changeSet when consider different situations
  - start update on a cluster which all machines are on target state
  - start update on a cluster with inconsistent state, some machines updated, some are not
- How to arrange the sequence of updater
- Are all current Infra providers assume Machine spec immutable?
- How to create a generic k8s component upgrade updater?
  - How to expose shell interface from infra provider?


## Sequence diagram
```plantuml
@startuml
actor User as user
participant "Toplology\ncontroller" as top
participant "InPlaceUpdatePlan\ncontroller" as clustert
participant "ControlPlane\ncontroller" as cpt
participant "MachineDeployment\ncontroller" as mdt
participant "Machine\ncontroller" as mt
box external
collections updaters
end box
activate top
activate cpt
activate mdt
activate mt

user -> top: request update
loop iterate updaters
  top -> updaters: call Lifecycle Runtime hook\n**ExternalUpdateRequest**
  updaters --> top: accept with `updaterName` `responsibilities` and `updateTaskTemplate` 
end
alt#white #pink not all change picked
  top -> top: fallback to rollout\nor mark update failed
end

top -> clustert: create inPlaceUpdateTask
activate clustert

alt no ongoing update
  clustert -> cpt: set `update-task-name` label
  clustert -> mdt: set `update-task-name` label
else #pink has ongoing update
  clustert -> clustert: wait and reenqueue
end

clustert -> cpt: set updatePhase to start\nsequence 1 update: [cluster1-cp]

loop iterate machines
  cpt -> cpt: preflight check
  cpt -> mt: pick 1 machine, set `update-task-name` label\nand updatePhase
  loop sort by sequence and iterate updater 
    mt -> updaters: create updaterUpdateTask
    activate updaters
    updaters -> updaters: operate on\nmachine
    updaters --> mt: report .status.updateStatus
    deactivate updaters
  end
  mt --> cpt: report .status.updateStatus
end
cpt --> clustert: report .status.updateStatus

clustert -> mdt: set updatePhase to start\nsequence 2 update: [cluster-md1, cluster-md2]
mdt -> mdt: create MachineSet with new spec
loop iterate machines
  mdt -> mdt: preflight check
  mdt -> mt: pick 1 machine, set `update-task-name` label\nand updatePhase
  loop sort by sequence and iterate updater 
    mt -> updaters: create updaterUpdateTask\nand set owner
    activate updaters
    updaters -> updaters: operate on\nmachine
    updaters --> mt: report .status.updateStatus
    deactivate updaters
  end
  mt --> mdt: report .status.updateStatus
  mdt -> mt: move machine from old MachineSet to\nnew MachineSet
end

mdt --> clustert: report .status.updateStatus

clustert -> clustert: update .status.updateStatus
deactivate clustert
@enduml
```


## Webhook definitions
```
controlPlaneUpdaterequest {
   clusterName
   controlplaneName
   changeSet
}

machineDeploymentUpdaterequest {
   clusterName
   machineDeploymentName
   changeSet
}

response {
   status: accept|reject|error
   message
   responsibilities
   updaterName
   updateTaskTemplateRef
}
```

## CRD definitions


```yaml
---
# InPlaceUpdatePlanController is responsible for reconcile in-place update on cluster level
# reconcile:
# - if updatePhase == `updating`
#   - set cluster.x-k8s.io/update-task-name label for cp/md to be updated
#   - aggregate cp/md update status
#   - if cp/md in current update sequence finished, start cp/md update in next sequence num
# - if updatePhase == `aborting`
#   - change updatePhase == `aborting` for cp/md which is in `updating` phase
#   - remove cluster.x-k8s.io/update-task-name label
InPlaceUpdatePlan
spec:
  updatePhase: updating|aborting
  controlPlane:
    name: cluster-cp1
    sequence: 1
    updaters:
    - name: osUpdater
      updaterUpdateTemplateRef:
        apiVersion: capzupdate.cluster.x-k8s.io/v1beta1
        kind: capzVmOsUpdateTaskTemplate
        name: capzVmOsUpdateTask
      sequence: 1
      responsibilities: 
      - path: /spec/image
        selectResource: InfraMachine
        value: azurelinux3.0-240602-k8s
    - name: versionUpdater
      updaterUpdateTemplateRef:
        apiVersion: update.cluster.x-k8s.io/v1beta1
        kind: nodeVersionUpdateTaskTemplate
        name: nodeVersionUpdateTask-control-plane
      sequence: 3
      responsibilities: 
      - path: /spec/version
        selectResource: Machine
        value: v1.30.1
    - name: kubeadmNtpUpdater
      updaterUpdateTemplateRef:
        apiVersion: kubeadmupdate.cluster.x-k8s.io/v1beta1
        kind: kubeadmNtpUpdateTaskTemplate
        name: kubeadmNtpUpdateTask
      sequence: 2
      responsibilities: 
      - path: /spec/NTP
        selectResource: Bootstrap
        value: |
          server:
          - time-a-g.nist.gov
          enabled: true
  machineDeployments:
  - name: cluster1-md1
    sequence: 2
    updaters: [...] # ignored
  - name: cluster1-md2
    sequence: 2
    updaters: [...] # ignored
status:
  updateStatus: updating|updated|aborting|aborted
  conditions:
  - type: controlPlaneUpdate
  - type: machineDeploymentUpdate

---
# KubeadmControlPlaneController is responsible for reconcile in-place update on kubeadmControlPlane
# reconcile:
# - preflight check
# - aggregate machine in-inplace update status
# - pick a machine and do in-place update
# - if updatePhase == `aborting`, change updatePhase == `aborting` for machine which is in `updating` phase
KubeadmControlPlane
metadata:
  labels:
    cluster.x-k8s.io/update-task-name: updatetask1
spec:
  updatePhase: updating|aborting
status:
  updateStatus: updating|updated|aborting|aborted
  conditions:
  - type: InPlaceUpdate # aggregate from machine InPlaceUpdate condition
  
---
# MachineDeploymentController is responsible for reconcile in-place update on machinedeployment
# reconcile:
# - create machineSet with new Spec
# - move updated machine from old machineSet to new machineSet
# - preflight check
# - aggregate machine in-inplace update status
# - pick a machine and do in-place update
# - if updatePhase == `aborting`, change updatePhase == `aborting` for machine which is in `updating` phase
MachineDeployment
metadata:
  labels:
    cluster.x-k8s.io/update-task-name: updatetask1
spec:
  updatePhase: updating|aborting
status:
  updateStatus: updating|updated|aborting|aborted
  conditions:
  - type: InPlaceUpdate # aggregate from machine InPlaceUpdate condition

---
# MachineInPlaceUpdateController is responsible for reconcile in-place update on machine level
# reconcile:
# - orchestrate updater and create updaterUpdateTask
# - when updatePhase == `updating`, aggregate updaterUpdateTask status, then update `updateStatus` field and `InPlaceUpdate` condition
# - when updatePhase == `aborting`, abort updaterUpdateTask, aggregate updaterUpdateTask status, then then update `updateStatus` field and `InPlaceUpdate` condition (timeout may need)
Machine:
metadata:
  labels:
    cluster.x-k8s.io/update-task-name: updatetask1
spec:
  updatePhase: updating|aborting
status:
  updateStatus: updating|updated|aborting|aborted
  condition:
  - type: InPlaceUpdate # aggregate from updaterUpdateTasks

---
# the contract of UpdaterUpdateTask.
# it MUST has spec.updatePhase to indicate the reconcile target, and has updateStatus to indicate the reconcile result 
# it SHALL have condition to report if error happens and detailed reason 
MachineUpdateContract:
spec:
  updatePhase: updating|aborting
status:
  updateStatus: updating|updated|aborting|aborted
  conditions:

---
# updaterUpdateTask is responsbile for part of update work on target machine
# responsibility is decided in ControlPlaneInPlaceUpdateTask/MachineDeploymentInPlaceUpdateTask responsibilities field.
# reoncile: 
# - when updatePhase == `updating`, update machine based on responsiblity, then update `updateStatus` field and `ready` condition
# - when updatePhase == `aborting`, abort update operation if has, then then update `updateStatus` field and `ready` condition
CapzVmOsUpdateTask
metadata:
  labels:
    cluster.x-k8s.io/cluster-name: cluster1
    cluster.x-k8s.io/control-plane-name: cluster1-cp
    cluster.x-k8s.io/update-task-name: updatetask1
    cluster.x-k8s.io/machine-name: cluster1-cp-1
    cluster.x-k8s.io/updater-name: versionUpdater
spec:
  updatePhase: updating
status:
  updateStatus: updated
  conditions:
  - type: 
```

### remediation snipt

One thing to notice, after using **external remediator** to recover machine, the machine may still use the old spec, so it's necessary to rerun the inplace update plan on remediated machine.

Another thing, Machine health check rules shall be extended so that it can monitor the machine in-place update progress. 

#### Machine Health Check
- It shall be a way to identify if a machine is under inplace update, and when it's started.
- A inplace update timeout setting shall be added to MHC spec. When a machine stays at inplace update stage for too long and exceeds the timeout threshold, MHC shall mark machine `HealthCheckSucceeded` Condition to false.
- A contract with external updater is needed to determine if update operation fails and in terminate state. When a machine fall into this state, MHC shall mark machine `HealthCheckSucceeded` condition to false. [TODO: need consider this in contract design]
- When calculating allowed remediation count, it shall treat the updating machines as `unhealthy`, because it's expected that updating machine may offline during update. ` RemediationCount = maxAllowedUnhealthy - unhealthyMachineCount - inUpdateHealthyMachineCount`
- Corner case 1: A new machine which is not initialized yet and in updating stage, timeout shall be `initializeTimeout + inPlaceUpdateTimeout` 