@@ -2,6 +2,9 @@ package machine_config
22
33import (
44 "context"
5+ "encoding/json"
6+ "fmt"
7+ "regexp"
58 "strings"
69 "time"
710
@@ -14,6 +17,8 @@ import (
1417 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1518 "k8s.io/kubernetes/test/e2e/framework"
1619 e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
20+
21+ streammeta "github.com/coreos/stream-metadata-go/stream"
1722)
1823
1924func AllMachineSetTest (oc * exutil.CLI , fixture string ) {
@@ -159,3 +164,164 @@ func EnsureConfigMapStampTest(oc *exutil.CLI, fixture string) {
159164 }, 2 * time .Minute , 5 * time .Second ).Should (o .BeTrue ())
160165 framework .Logf ("Succesfully verified that the configmap has been correctly stamped" )
161166}
167+
168+ func UploadTovCentreTest (oc * exutil.CLI , fixture string ) {
169+
170+ // This fixture applies a boot image update configuration that opts in any machineset with the label test=boot
171+ ApplyMachineConfigurationFixture (oc , fixture )
172+
173+ // Pick a random machineset to test
174+ machineClient , err := machineclient .NewForConfig (oc .KubeFramework ().ClientConfig ())
175+ o .Expect (err ).NotTo (o .HaveOccurred ())
176+ machineSetUnderTest := getRandomMachineSet (machineClient )
177+ framework .Logf ("MachineSet under test: %s" , machineSetUnderTest .Name )
178+
179+ // Label this machineset with the test=boot label
180+ err = oc .Run ("label" ).Args (mapiMachinesetQualifiedName , machineSetUnderTest .Name , "-n" , mapiNamespace , "test=boot" ).Execute ()
181+ o .Expect (err ).NotTo (o .HaveOccurred ())
182+ defer func () {
183+ // Unlabel the machineset at the end of test
184+ err = oc .Run ("label" ).Args (mapiMachinesetQualifiedName , machineSetUnderTest .Name , "-n" , mapiNamespace , "test-" ).Execute ()
185+ o .Expect (err ).NotTo (o .HaveOccurred ())
186+ }()
187+
188+ // Modify coreos-bootimage cm to an older known version (transient application since CVO should revert immediately)
189+ cm , err := oc .AdminKubeClient ().CoreV1 ().ConfigMaps (mcoNamespace ).Get (context .TODO (), cmName , metav1.GetOptions {})
190+ o .Expect (err ).NotTo (o .HaveOccurred ())
191+ var stream streammeta.Stream
192+ err = json .Unmarshal ([]byte (cm .Data ["stream" ]), & stream )
193+ o .Expect (err ).NotTo (o .HaveOccurred ())
194+ vmware := stream .Architectures ["x86_64" ].Artifacts ["vmware" ]
195+ currentVmwareRelease := vmware .Release
196+ vmware .Release = "418.94.202501221327-0"
197+ vmware .Formats ["ova" ].Disk .Location = "https://rhcos.mirror.openshift.com/art/storage/prod/streams/4.18-9.4/builds/418.94.202501221327-0/x86_64/rhcos-418.94.202501221327-0-vmware.x86_64.ova"
198+ vmware .Formats ["ova" ].Disk .Sha256 = "6fd6e9fa2ff949154d54572c3f6a0c400f3e801457aa88b585b751a0955bda19"
199+ stream .Architectures ["x86_64" ].Artifacts ["vmware" ] = vmware
200+ updatedStreamBytes , err := json .MarshalIndent (stream , "" , " " )
201+ o .Expect (err ).NotTo (o .HaveOccurred ())
202+ escapedStreamBytes , err := json .Marshal (string (updatedStreamBytes ))
203+ o .Expect (err ).NotTo (o .HaveOccurred ())
204+ patchPayload := fmt .Sprintf (`{"data":{"stream":%s}}` , string (escapedStreamBytes ))
205+ err = oc .Run ("patch" ).Args ("configmap" , cmName , "-n" , mcoNamespace , "-p" , patchPayload ).Execute ()
206+ o .Expect (err ).NotTo (o .HaveOccurred ())
207+
208+ // Ensure boot image controller is not progressing
209+ framework .Logf ("Waiting until the boot image controller is not progressing..." )
210+ WaitForBootImageControllerToComplete (oc )
211+
212+ // Ensure MSBIC moves successfully from current bootimage -> known old bootimage -> current bootimage
213+ currentToOldLog := fmt .Sprintf ("Existing RHCOS v%s does not match current RHCOS v%s. Starting reconciliation process." , currentVmwareRelease , vmware .Release )
214+ oldToCurrentLog := fmt .Sprintf ("Existing RHCOS v%s does not match current RHCOS v%s. Starting reconciliation process." , vmware .Release , currentVmwareRelease )
215+ successfullyPatchedLog := fmt .Sprintf ("Successfully patched machineset %s" , machineSetUnderTest .Name )
216+ o .Eventually (func () bool {
217+ podNames , err := oc .Run ("get" ).Args (
218+ "pods" ,
219+ "-n" , "openshift-machine-config-operator" ,
220+ "-l" , "k8s-app=machine-config-controller" ,
221+ "-o" , "go-template={{range .items}}{{.metadata.name}}{{\" \\ n\" }}{{end}}" ,
222+ ).Output ()
223+ if err != nil {
224+ return false
225+ }
226+ podNamesArr := strings .Split (podNames , "\n " )
227+ if len (podNamesArr ) == 0 {
228+ return false
229+ }
230+ mccPodName := podNamesArr [0 ]
231+ logs , err := oc .Run ("logs" ).Args (mccPodName , "-n" , "openshift-machine-config-operator" , "--tail=50" ).Output ()
232+ if err != nil {
233+ return false
234+ }
235+ return checkOrder (logs , currentToOldLog , successfullyPatchedLog , oldToCurrentLog , successfullyPatchedLog )
236+ }, 15 * time .Minute , 10 * time .Second ).Should (o .BeTrue ())
237+
238+ // Scale-up the machineset to verify we have not accidentally broken the bootimage updates
239+ mcdPods , err := getMCDPodSet (oc )
240+ err = oc .Run ("scale" ).Args (mapiMachinesetQualifiedName , machineSetUnderTest .Name , "-n" , mapiNamespace , fmt .Sprintf ("--replicas=%d" , * machineSetUnderTest .Spec .Replicas + 1 )).Execute ()
241+ o .Expect (err ).NotTo (o .HaveOccurred ())
242+
243+ defer func () {
244+ // Scale-down the machineset at the end of test
245+ err = oc .Run ("scale" ).Args (mapiMachinesetQualifiedName , machineSetUnderTest .Name , "-n" , mapiNamespace , fmt .Sprintf ("--replicas=%d" , * machineSetUnderTest .Spec .Replicas )).Execute ()
246+ o .Expect (err ).NotTo (o .HaveOccurred ())
247+ }()
248+
249+ o .Eventually (func () bool {
250+ machineset , err := machineClient .MachineV1beta1 ().MachineSets (mapiNamespace ).Get (context .TODO (), machineSetUnderTest .Name , metav1.GetOptions {})
251+ if err != nil {
252+ return false
253+ }
254+ return machineset .Status .AvailableReplicas == * machineSetUnderTest .Spec .Replicas + 1
255+ }, 15 * time .Minute , 10 * time .Second ).Should (o .BeTrue ())
256+
257+ o .Eventually (func () bool {
258+ updatedMcdPods , err := getMCDPodSet (oc )
259+ if err != nil {
260+ return false
261+ }
262+ newPods := diffNewPods (mcdPods , updatedMcdPods )
263+ if len (newPods ) == 0 {
264+ return false
265+ }
266+ for _ , pod := range newPods {
267+ logs , err := oc .Run ("logs" ).Args (pod , "-n" , "openshift-machine-config-operator" ).Output ()
268+ if err != nil {
269+ continue
270+ }
271+ lines := strings .Split (logs , "\n " )
272+ if len (lines ) > 50 {
273+ lines = lines [:50 ]
274+ }
275+ first50 := strings .Join (lines , "\n " )
276+ if strings .Contains (first50 , currentVmwareRelease ) {
277+ return true
278+ }
279+ }
280+ return false
281+ }, 15 * time .Minute , 10 * time .Second ).Should (o .BeTrue ())
282+ }
283+
284+ // checkOrder returns true if all statements appear in the log in the given order.
285+ func checkOrder (log string , statements ... string ) bool {
286+ if len (statements ) == 0 {
287+ return false
288+ }
289+ parts := make ([]string , len (statements ))
290+ for i , s := range statements {
291+ parts [i ] = regexp .QuoteMeta (s ) // escape regex metacharacters
292+ }
293+ pattern := "(?s)" + strings .Join (parts , ".*" ) // allow anything between statements
294+ return regexp .MustCompile (pattern ).MatchString (log )
295+ }
296+
297+ func diffNewPods (before , after map [string ]struct {}) []string {
298+ var newPods []string
299+ for pod := range after {
300+ if _ , found := before [pod ]; ! found {
301+ newPods = append (newPods , pod )
302+ }
303+ }
304+ return newPods
305+ }
306+
307+ func getMCDPodSet (oc * exutil.CLI ) (map [string ]struct {}, error ) {
308+ out , err := oc .Run ("get" ).Args (
309+ "pods" ,
310+ "-n" , "openshift-machine-config-operator" ,
311+ "-l" , "k8s-app=machine-config-daemon" ,
312+ "-o" , "go-template={{range .items}}{{.metadata.name}}{{\" \\ n\" }}{{end}}" ,
313+ ).Output ()
314+ if err != nil {
315+ return nil , fmt .Errorf ("failed to get pods: %v" , err )
316+ }
317+
318+ podSet := make (map [string ]struct {})
319+ lines := strings .Split (out , "\n " )
320+ for _ , line := range lines {
321+ name := strings .TrimSpace (line )
322+ if name != "" {
323+ podSet [name ] = struct {}{}
324+ }
325+ }
326+ return podSet , nil
327+ }
0 commit comments