@@ -4,15 +4,24 @@ package cvo
44
55import (
66 "context"
7+ "fmt"
8+ "strings"
9+ "time"
710
11+ "github.com/blang/semver/v4"
812 g "github.com/onsi/ginkgo/v2"
913 o "github.com/onsi/gomega"
1014
1115 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16+ "k8s.io/apimachinery/pkg/util/wait"
1217 "k8s.io/client-go/kubernetes"
1318 "k8s.io/client-go/rest"
1419
20+ configv1 "github.com/openshift/api/config/v1"
21+ clientconfigv1 "github.com/openshift/client-go/config/clientset/versioned"
22+
1523 "github.com/openshift/cluster-version-operator/pkg/external"
24+ "github.com/openshift/cluster-version-operator/pkg/payload/precondition/clusterversion"
1625 "github.com/openshift/cluster-version-operator/test/oc"
1726 ocapi "github.com/openshift/cluster-version-operator/test/oc/api"
1827 "github.com/openshift/cluster-version-operator/test/util"
@@ -99,4 +108,202 @@ var _ = g.Describe(`[Jira:"Cluster Version Operator"] cluster-version-operator`,
99108 sccAnnotation := cvoPod .Annotations ["openshift.io/scc" ]
100109 o .Expect (sccAnnotation ).To (o .Equal ("hostaccess" ), "Expected the annotation 'openshift.io/scc annotation' on pod %s to have the value 'hostaccess', but got %s" , cvoPod .Name , sccAnnotation )
101110 })
111+
112+ g .It ("cvo drops invalid conditional edges" , g .Label ("46422" , "Low" , "ConnectedOnly" , "Serial" ), func () {
113+ ctx := context .Background ()
114+ err := util .SkipIfNetworkRestricted (ctx , restCfg , util .FauxinnatiAPIURL )
115+ o .Expect (err ).NotTo (o .HaveOccurred (), "Failed to check network connectivity" )
116+
117+ clientconfigv1 , err := clientconfigv1 .NewForConfig (restCfg )
118+ o .Expect (err ).NotTo (o .HaveOccurred ())
119+ cv , err := clientconfigv1 .ConfigV1 ().ClusterVersions ().Get (ctx , "version" , metav1.GetOptions {})
120+ o .Expect (err ).NotTo (o .HaveOccurred ())
121+ o .Expect (cv ).NotTo (o .BeNil ())
122+
123+ versionString , image := clusterversion .GetCurrentVersionAndImage (cv .Status .History )
124+ o .Expect (versionString ).NotTo (o .BeNil ())
125+ o .Expect (image ).NotTo (o .BeNil ())
126+
127+ ver , err := semver .Make (versionString )
128+ o .Expect (err ).NotTo (o .HaveOccurred ())
129+ channel := fmt .Sprintf ("candidate-%d.%d" , ver .Major , ver .Minor )
130+
131+ nodes := []util.Node {
132+ {Version : versionString , Payload : image , Channel : channel },
133+ }
134+ edges := []util.Edge {}
135+ conditionalEdges := []util.ConditionalEdge {
136+ {
137+ Edge : util.StringEdge {
138+ From : versionString ,
139+ To : "" ,
140+ },
141+ Risks : []util.Risk {
142+ {
143+ Url : "https://bugzilla.redhat.com/show_bug.cgi?id=123456" ,
144+ Name : "Bug 123456" ,
145+ Message : "Empty target node" ,
146+ Rule : map [string ]interface {}{"type" : "Always" },
147+ },
148+ },
149+ },
150+ }
151+ g .By ("create upstream graph template with invalid conditional edges" )
152+ buf , err := util .CreateGraphTemplate (nodes , edges , conditionalEdges )
153+ o .Expect (err ).NotTo (o .HaveOccurred ())
154+
155+ label := map [string ]string {"app" : "test-update-service" }
156+ g .By ("run update service with the graph template" )
157+ deployment1 , err := util .RunUpdateService (ctx , kubeClient , external .DefaultCVONamespace , buf .String (), label )
158+ o .Expect (err ).NotTo (o .HaveOccurred ())
159+ logger .Info (fmt .Sprintf ("deployment1: %s, %s, %d" , deployment1 .Spec .Template .Spec .Containers [0 ].Image , deployment1 .Spec .Template .Spec .Containers [0 ].Ports [0 ].Name , deployment1 .Spec .Template .Spec .Containers [0 ].Ports [0 ].ContainerPort ))
160+
161+ service1 , url , err := util .CreateService (ctx , kubeClient , external .DefaultCVONamespace , deployment1 , 46422 )
162+ o .Expect (err ).NotTo (o .HaveOccurred ())
163+ logger .Info ("Update Service URL: " + url .String ())
164+ logger .Info (fmt .Sprintf ("Service: %s, %v, %v" , service1 .Spec .Ports [0 ].Name , & service1 .Spec .Ports [0 ].Port , service1 .Spec .Ports [0 ].TargetPort ))
165+
166+ policyName := service1 .Name + "-policy"
167+ _ , err = util .CreateNetworkPolicy (ctx , kubeClient , external .DefaultCVONamespace , policyName , label )
168+ o .Expect (err ).NotTo (o .HaveOccurred ())
169+
170+ pollErr := wait .PollUntilContextTimeout (ctx , 5 * time .Second , 60 * time .Second , true , func (ctx context.Context ) (bool , error ) {
171+ if res , getErr := util .NetworkRestricted (ctx , restCfg , url .String ()); getErr != nil {
172+ return false , getErr
173+ } else if res {
174+ return false , fmt .Errorf ("expected network to be available, but it is not" )
175+ }
176+ return true , nil
177+ })
178+ o .Expect (pollErr ).NotTo (o .HaveOccurred (), fmt .Sprintf ("Failed to verify network connectivity to the update service at %s: %v" , url .String (), pollErr ))
179+
180+ g .By ("patch upstream with null target node conditional edge" )
181+ o .Expect (err ).NotTo (o .HaveOccurred ())
182+ _ , err = util .PatchUpstream (ctx , clientconfigv1 , url .String (), channel )
183+ o .Expect (err ).NotTo (o .HaveOccurred ())
184+ pollErr = wait .PollUntilContextTimeout (ctx , 5 * time .Second , 1 * time .Minute , true , func (ctx context.Context ) (bool , error ) {
185+ cv , err := clientconfigv1 .ConfigV1 ().ClusterVersions ().Get (ctx , "version" , metav1.GetOptions {})
186+ if err != nil {
187+ return false , err
188+ }
189+ for _ , condition := range cv .Status .Conditions {
190+ if condition .Type == configv1 .ClusterStatusConditionType ("RetrievedUpdates" ) {
191+ if condition .Status != configv1 .ConditionFalse {
192+ return false , nil
193+ }
194+ if ! strings .Contains (condition .Message , "no node for conditional update" ) {
195+ return false , nil
196+ }
197+ return true , nil
198+ }
199+ }
200+ return false , nil
201+ })
202+ o .Expect (pollErr ).NotTo (o .HaveOccurred (), fmt .Sprintf ("Failed to verify the cluster version condition for the null target node conditional edge: %v" , pollErr ))
203+
204+ g .By ("stop update service" )
205+ defer func () { _ = util .StopUpdateService (ctx , kubeClient , external .DefaultCVONamespace , deployment1 ) }()
206+ defer func () { _ = util .CleanupService (ctx , kubeClient , external .DefaultCVONamespace , service1 .Name ) }()
207+ defer func () { _ = util .DeleteDeployment (ctx , kubeClient , external .DefaultCVONamespace , deployment1 .Name ) }()
208+
209+ targetVersion := fmt .Sprintf ("%d.%d.999" , ver .Major , ver .Minor )
210+ nodes = append (nodes , util.Node {
211+ Version : targetVersion ,
212+ Payload : "quay.io/openshift-release-dev/ocp-release@sha256:fc88d0bf145c81d9c9e5b8a1cfcaa7cbbacfa698f0c3a252fbbdbfbb1b2" ,
213+ Channel : channel ,
214+ })
215+ edges = []util.Edge {}
216+ conditionalEdges = []util.ConditionalEdge {
217+ {
218+ Edge : util.StringEdge {
219+ From : versionString ,
220+ To : targetVersion ,
221+ },
222+ Risks : []util.Risk {
223+ {
224+ Url : "// example.com" ,
225+ Name : "InvalidURL" ,
226+ Message : "Invalid URL." ,
227+ Rule : map [string ]interface {}{"type" : "PromQL" , "promql" : map [string ]interface {}{"promql" : "cluster_installer" }},
228+ },
229+ {
230+ Url : "https://bug.example.com/b" ,
231+ Name : "TypeNull" ,
232+ Message : "MatchingRules type is null." ,
233+ Rule : "{\" type\" : \" \" }" ,
234+ },
235+ {
236+ Url : "https://bug.example.com/c" ,
237+ Name : "InvalidMatchingRulesType" ,
238+ Message : "MatchingRules type is invalid, support Always and PromQL." ,
239+ Rule : map [string ]interface {}{"type" : "nonexist" , "promql" : map [string ]interface {}{"promql" : "group(cluster_version_available_updates{channel=\" buggy\" })\n or\n 0 * group(cluster_version_available_updates{channel!=\" buggy\" })" }},
240+ },
241+ {
242+ Url : "https://bug.example.com/d" ,
243+ Name : "InvalidPromQLQueryReturnValue" ,
244+ Message : "PromQL query return value is not supported, support 0 and 1." ,
245+ Rule : map [string ]interface {}{"type" : "PromQL" , "promql" : map [string ]interface {}{"promql" : "max(cluster_version)" }},
246+ },
247+ {
248+ Url : "https://bug.example.com/d" ,
249+ Name : "InvalidPromQLQuery" ,
250+ Message : "Invalid PromQL Query." ,
251+ Rule : map [string ]interface {}{"type" : "PromQL" , "promql" : map [string ]interface {}{"promql" : "cluster_infrastructure_provider{type=~\" VSphere|None\" }" }},
252+ },
253+ },
254+ },
255+ }
256+ g .By ("create upstream graph template with multi risks conditional edges" )
257+ buf , err = util .CreateGraphTemplate (nodes , edges , conditionalEdges )
258+ o .Expect (err ).NotTo (o .HaveOccurred ())
259+
260+ g .By ("run update service with the multi risks graph template" )
261+ deployment2 , err := util .RunUpdateService (ctx , kubeClient , external .DefaultCVONamespace , buf .String (), label )
262+ o .Expect (err ).NotTo (o .HaveOccurred ())
263+ service2 , url2 , err := util .CreateService (ctx , kubeClient , external .DefaultCVONamespace , deployment2 , 46422 )
264+ o .Expect (err ).NotTo (o .HaveOccurred ())
265+ logger .Info ("Update Service URL: " + url2 .String ())
266+ logger .Info (fmt .Sprintf ("Service: %s, %v, %v" , service2 .Spec .Ports [0 ].Name , & service2 .Spec .Ports [0 ].Port , service2 .Spec .Ports [0 ].TargetPort ))
267+
268+ pollErr = wait .PollUntilContextTimeout (ctx , 5 * time .Second , 60 * time .Second , true , func (ctx context.Context ) (bool , error ) {
269+ if res , getErr := util .NetworkRestricted (ctx , restCfg , url2 .String ()); getErr != nil {
270+ return false , getErr
271+ } else if res {
272+ return false , fmt .Errorf ("expected network to be available, but it is not" )
273+ }
274+ return true , nil
275+ })
276+ o .Expect (pollErr ).NotTo (o .HaveOccurred (), fmt .Sprintf ("Failed to verify network connectivity to the update service at %s: %v" , url2 .String (), pollErr ))
277+
278+ g .By ("patch upstream with multi risks conditional edge" )
279+ _ , err = util .PatchUpstream (ctx , clientconfigv1 , url2 .String (), channel )
280+ o .Expect (err ).NotTo (o .HaveOccurred ())
281+ pollErr = wait .PollUntilContextTimeout (ctx , 5 * time .Second , 1 * time .Minute , true , func (ctx context.Context ) (bool , error ) {
282+ cv , err := clientconfigv1 .ConfigV1 ().ClusterVersions ().Get (ctx , "version" , metav1.GetOptions {})
283+ if err != nil {
284+ return false , err
285+ }
286+ if cv .Status .AvailableUpdates != nil {
287+ return false , nil
288+ }
289+ for _ , condition := range cv .Status .Conditions {
290+ if condition .Type == configv1 .ClusterStatusConditionType ("RetrievedUpdates" ) {
291+ if condition .Status != configv1 .ConditionTrue {
292+ return false , nil
293+ }
294+ return true , nil
295+ }
296+ }
297+ return true , nil
298+ })
299+ o .Expect (pollErr ).NotTo (o .HaveOccurred (), fmt .Sprintf ("Failed to verify the cluster version condition for the multi risks target node conditional edge: %v" , pollErr ))
300+
301+ g .By ("stop update service again" )
302+ defer func () { _ = util .StopUpdateService (ctx , kubeClient , external .DefaultCVONamespace , deployment2 ) }()
303+ defer func () { _ = util .CleanupService (ctx , kubeClient , external .DefaultCVONamespace , service2 .Name ) }()
304+ defer func () { _ = util .DeleteDeployment (ctx , kubeClient , external .DefaultCVONamespace , deployment2 .Name ) }()
305+ defer func () {
306+ _ = util .DeleteNetworkPolicy (ctx , kubeClient , external .DefaultCVONamespace , policyName )
307+ }()
308+ })
102309})
0 commit comments