@@ -19,15 +19,19 @@ import (
1919 "context"
2020 "errors"
2121 "fmt"
22+ "strings"
2223
2324 "github.com/crunchydata/postgres-operator/internal/apiserver"
2425 "github.com/crunchydata/postgres-operator/internal/config"
2526 "github.com/crunchydata/postgres-operator/internal/operator"
2627 "github.com/crunchydata/postgres-operator/internal/util"
2728 crv1 "github.com/crunchydata/postgres-operator/pkg/apis/crunchydata.com/v1"
2829 msgs "github.com/crunchydata/postgres-operator/pkg/apiservermsgs"
30+
2931 log "github.com/sirupsen/logrus"
32+ v1 "k8s.io/api/core/v1"
3033 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
34+ "k8s.io/apimachinery/pkg/fields"
3135)
3236
3337// CreateFailover is the API endpoint for triggering a manual failover of a
@@ -60,18 +64,24 @@ func CreateFailover(request *msgs.CreateFailoverRequest, ns, pgouser string) msg
6064 return resp
6165 }
6266
63- if request .Target != "" {
64- if err := isValidFailoverTarget (request .Target , request .ClusterName , ns ); err != nil {
65- resp .Status .Code = msgs .Error
66- resp .Status .Msg = err .Error ()
67- return resp
68- }
67+ if err := isValidFailoverTarget (request ); err != nil {
68+ resp .Status .Code = msgs .Error
69+ resp .Status .Msg = err .Error ()
70+ return resp
6971 }
7072
71- // perform the switchover
72- if err := operator .Switchover (apiserver .Clientset , apiserver .RESTConfig , cluster , request .Target ); err != nil {
73+ // perform the switchover or failover, depending on which flag is selected
74+ // if we are forcing the failover, we need to use "Failover", otherwise we
75+ // perform a controlled switchover
76+ if request .Force {
77+ err = operator .Failover (apiserver .Clientset , apiserver .RESTConfig , cluster , request .Target )
78+ } else {
79+ err = operator .Switchover (apiserver .Clientset , apiserver .RESTConfig , cluster , request .Target )
80+ }
81+
82+ if err != nil {
7383 resp .Status .Code = msgs .Error
74- resp .Status .Msg = err .Error ()
84+ resp .Status .Msg = strings . ReplaceAll ( err .Error (), "master" , "primary" )
7585 return resp
7686 }
7787
@@ -161,31 +171,53 @@ func validateClusterName(clusterName, ns string) (*crv1.Pgcluster, error) {
161171// specified, and then ensuring the PG pod created by the deployment is not the current primary.
162172// If the deployment is not found, or if the pod is the current primary, an error will be returned.
163173// Otherwise the deployment is returned.
164- func isValidFailoverTarget (deployName , clusterName , ns string ) error {
174+ func isValidFailoverTarget (request * msgs. CreateFailoverRequest ) error {
165175 ctx := context .TODO ()
166176
177+ // if we're not forcing a failover and the target is blank, we can
178+ // return here
179+ // However, if we are forcing a failover and the target is blank, then we do
180+ // have an error
181+ if request .Target == "" {
182+ if ! request .Force {
183+ return nil
184+ }
185+
186+ return fmt .Errorf ("target is required when forcing a failover." )
187+ }
188+
167189 // Using the following label selector, ensure the deployment specified using deployName exists in the
168190 // cluster specified using clusterName:
169191 // pg-cluster=clusterName,deployment-name=deployName
170- selector := config .LABEL_PG_CLUSTER + "=" + clusterName + "," + config .LABEL_DEPLOYMENT_NAME + "=" + deployName
171- deployments , err := apiserver .Clientset .
172- AppsV1 ().Deployments (ns ).
173- List (ctx , metav1.ListOptions {LabelSelector : selector })
192+ options := metav1.ListOptions {
193+ LabelSelector : fields .AndSelectors (
194+ fields .OneTermEqualSelector (config .LABEL_PG_CLUSTER , request .ClusterName ),
195+ fields .OneTermEqualSelector (config .LABEL_DEPLOYMENT_NAME , request .Target ),
196+ ).String (),
197+ }
198+ deployments , err := apiserver .Clientset .AppsV1 ().Deployments (request .Namespace ).List (ctx , options )
199+
174200 if err != nil {
175201 log .Error (err )
176202 return err
177203 } else if len (deployments .Items ) == 0 {
178- return fmt .Errorf ("no target found named %s" , deployName )
204+ return fmt .Errorf ("no target found named %s" , request . Target )
179205 } else if len (deployments .Items ) > 1 {
180- return fmt .Errorf ("more than one target found named %s" , deployName )
206+ return fmt .Errorf ("more than one target found named %s" , request . Target )
181207 }
182208
183209 // Using the following label selector, determine if the target specified is the current
184210 // primary for the cluster and return an error if it is:
185211 // pg-cluster=clusterName,deployment-name=deployName,role=primary
186- selector = config .LABEL_PG_CLUSTER + "=" + clusterName + "," + config .LABEL_DEPLOYMENT_NAME + "=" + deployName +
187- "," + config .LABEL_PGHA_ROLE + "=" + config .LABEL_PGHA_ROLE_PRIMARY
188- pods , _ := apiserver .Clientset .CoreV1 ().Pods (ns ).List (ctx , metav1.ListOptions {LabelSelector : selector })
212+ options .FieldSelector = fields .OneTermEqualSelector ("status.phase" , string (v1 .PodRunning )).String ()
213+ options .LabelSelector = fields .AndSelectors (
214+ fields .OneTermEqualSelector (config .LABEL_PG_CLUSTER , request .ClusterName ),
215+ fields .OneTermEqualSelector (config .LABEL_DEPLOYMENT_NAME , request .Target ),
216+ fields .OneTermEqualSelector (config .LABEL_PGHA_ROLE , config .LABEL_PGHA_ROLE_PRIMARY ),
217+ ).String ()
218+
219+ pods , _ := apiserver .Clientset .CoreV1 ().Pods (request .Namespace ).List (ctx , options )
220+
189221 if len (pods .Items ) > 0 {
190222 return fmt .Errorf ("The primary database cannot be selected as a failover target" )
191223 }
0 commit comments