@@ -252,6 +252,10 @@ func (s *sqlInstancesService) Insert(ctx context.Context, req *pb.SqlInstancesIn
252252 return nil , fmt .Errorf ("creating postgres user: %w" , err )
253253 }
254254 }
255+
256+ if err := s .ensureMasterReflectsReplica (ctx , * name , nil , obj ); err != nil {
257+ return nil , fmt .Errorf ("ensuring master reflects replica: %w" , err )
258+ }
255259 }
256260
257261 op := & pb.Operation {
@@ -267,6 +271,115 @@ func (s *sqlInstancesService) Insert(ctx context.Context, req *pb.SqlInstancesIn
267271 })
268272}
269273
274+ func (s * sqlInstancesService ) ensureMasterReflectsReplica (ctx context.Context , name InstanceName , oldInstance * pb.DatabaseInstance , newInstance * pb.DatabaseInstance ) error {
275+ // If this is a replica instance, we also need to update the master instance to add the replica name.
276+
277+ newMasterInstanceName := ""
278+ if newInstance != nil {
279+ newMasterInstanceName = newInstance .MasterInstanceName
280+ }
281+
282+ oldMasterInstanceName := ""
283+ if oldInstance != nil {
284+ oldMasterInstanceName = oldInstance .MasterInstanceName
285+ }
286+
287+ if newMasterInstanceName != "" && newMasterInstanceName != oldMasterInstanceName {
288+ masterName := name
289+
290+ tokens := strings .Split (newMasterInstanceName , ":" )
291+ if len (tokens ) >= 2 {
292+ masterName .Project = & projects.ProjectData {ID : tokens [0 ]}
293+ masterName .InstanceName = tokens [1 ]
294+ } else {
295+ masterName .InstanceName = tokens [0 ]
296+ }
297+
298+ masterFQN := masterName .String ()
299+
300+ master := & pb.DatabaseInstance {}
301+ if err := s .storage .Get (ctx , masterFQN , master ); err != nil {
302+ return fmt .Errorf ("getting old master instance: %w" , err )
303+ }
304+
305+ shouldUpdate := false
306+
307+ // Add to replicaNames
308+ {
309+ found := false
310+ replicaName := name .InstanceName
311+ if name .Project .ID != masterName .Project .ID {
312+ replicaName = name .Project .ID + ":" + name .InstanceName
313+ }
314+
315+ for _ , s := range master .ReplicaNames {
316+ if s == replicaName {
317+ found = true
318+ }
319+ }
320+ if ! found {
321+ master .ReplicaNames = append (master .ReplicaNames , replicaName )
322+ shouldUpdate = true
323+ }
324+ }
325+
326+ if shouldUpdate {
327+ if err := s .storage .Update (ctx , masterFQN , master ); err != nil {
328+ return fmt .Errorf ("updating old master instance: %w" , err )
329+ }
330+ }
331+ }
332+
333+ if oldMasterInstanceName != "" && newMasterInstanceName != oldMasterInstanceName {
334+ masterName := name
335+
336+ tokens := strings .Split (oldMasterInstanceName , ":" )
337+ if len (tokens ) >= 2 {
338+ masterName .Project = & projects.ProjectData {ID : tokens [0 ]}
339+ masterName .InstanceName = tokens [1 ]
340+ } else {
341+ masterName .InstanceName = tokens [0 ]
342+ }
343+
344+ masterFQN := masterName .String ()
345+
346+ master := & pb.DatabaseInstance {}
347+ if err := s .storage .Get (ctx , masterFQN , master ); err != nil {
348+ return fmt .Errorf ("getting old master instance: %w" , err )
349+ }
350+
351+ shouldUpdate := false
352+
353+ // Remove from replicaNames
354+ {
355+ var keep []string
356+ replicaName := name .InstanceName
357+ if name .Project .ID != masterName .Project .ID {
358+ replicaName = name .Project .ID + ":" + name .InstanceName
359+ }
360+
361+ for _ , s := range master .ReplicaNames {
362+ if s == replicaName {
363+ continue
364+ }
365+ keep = append (keep , s )
366+ }
367+ if len (keep ) != len (master .ReplicaNames ) {
368+ master .ReplicaNames = keep
369+ shouldUpdate = true
370+ }
371+ }
372+
373+ if shouldUpdate {
374+ if err := s .storage .Update (ctx , masterFQN , master ); err != nil {
375+ return fmt .Errorf ("updating old master instance: %w" , err )
376+ }
377+ }
378+ }
379+
380+ return nil
381+ }
382+
270383func setDefaultInt64 (pp * * wrapperspb.Int64Value , defaultValue int64 ) {
271384 if * pp == nil {
272385 * pp = & wrapperspb.Int64Value {
@@ -541,7 +654,11 @@ func populateDefaults(obj *pb.DatabaseInstance) {
541654 }
542655
543656 if obj .InstanceType == pb .SqlInstanceType_SQL_INSTANCE_TYPE_UNSPECIFIED {
544- obj .InstanceType = pb .SqlInstanceType_CLOUD_SQL_INSTANCE
657+ if obj .MasterInstanceName != "" {
658+ obj .InstanceType = pb .SqlInstanceType_READ_REPLICA_INSTANCE
659+ } else {
660+ obj .InstanceType = pb .SqlInstanceType_CLOUD_SQL_INSTANCE
661+ }
545662 }
546663
547664 if obj .GeminiConfig == nil {
@@ -940,6 +1057,95 @@ func (s *sqlInstancesService) Update(ctx context.Context, req *pb.SqlInstancesUp
9401057 })
9411058}
9421059
1060+ func (s * sqlInstancesService ) Switchover (ctx context.Context , req * pb.SqlInstancesSwitchoverRequest ) (* pb.Operation , error ) {
1061+ name , err := s .buildInstanceName (req .GetProject (), req .GetInstance ())
1062+ if err != nil {
1063+ return nil , err
1064+ }
1065+
1066+ fqn := name .String ()
1067+
1068+ obj := & pb.DatabaseInstance {}
1069+ if err := s .storage .Get (ctx , fqn , obj ); err != nil {
1070+ return nil , err
1071+ }
1072+
1073+ oldMasterInstanceName := obj .MasterInstanceName
1074+ if oldMasterInstanceName == "" {
1075+ return nil , status .Errorf (codes .FailedPrecondition , "Invalid request: instance is not a replica instance." )
1076+ }
1077+
1078+ // A switchover makes the old master a replica of the new master, so we need to update the old master instance accordingly.
1079+ {
1080+ oldMasterName := * name
1081+
1082+ tokens := strings .Split (oldMasterInstanceName , ":" )
1083+ if len (tokens ) >= 2 {
1084+ oldMasterName .Project = & projects.ProjectData {ID : tokens [0 ]}
1085+ oldMasterName .InstanceName = tokens [1 ]
1086+ } else {
1087+ oldMasterName .InstanceName = oldMasterInstanceName
1088+ }
1089+
1090+ oldMasterFQN := oldMasterName .String ()
1091+
1092+ oldMaster := & pb.DatabaseInstance {}
1093+ if err := s .storage .Get (ctx , oldMasterFQN , oldMaster ); err != nil {
1094+ return nil , fmt .Errorf ("getting old master instance: %w" , err )
1095+ }
1096+
1097+ // Swap FailoverReplica
1098+ obj .FailoverReplica = oldMaster .FailoverReplica
1099+ oldMaster .FailoverReplica = nil
1100+
1101+ // Swap InstanceType
1102+ obj .InstanceType = pb .SqlInstanceType_CLOUD_SQL_INSTANCE
1103+ oldMaster .InstanceType = pb .SqlInstanceType_READ_REPLICA_INSTANCE
1104+
1105+ // Swap MasterInstanceName
1106+ obj .MasterInstanceName = ""
1107+ oldMaster .MasterInstanceName = name .Project .ID + ":" + name .InstanceName
1108+
1109+ // Swap ReplicationCluster
1110+ obj .ReplicationCluster = oldMaster .ReplicationCluster
1111+ oldMaster .ReplicationCluster = nil
1112+
1113+ // Set replica names
1114+ replicaName := oldMasterName .InstanceName
1115+ if oldMasterName .Project .ID != name .Project .ID {
1116+ replicaName = oldMasterName .Project .ID + ":" + oldMasterName .InstanceName
1117+ }
1118+ obj .ReplicaNames = []string {replicaName }
1119+ oldMaster .ReplicaNames = nil
1120+
1121+ oldMaster .Etag = fields .ComputeWeakEtag (oldMaster )
1122+
1123+ // Update the old master
1124+ if err := s .storage .Update (ctx , oldMasterFQN , oldMaster ); err != nil {
1125+ return nil , fmt .Errorf ("updating old master instance: %w" , err )
1126+ }
1127+ }
1128+
1129+ obj .Etag = fields .ComputeWeakEtag (obj )
1130+
1131+ if err := validateDatabaseInstance (obj ); err != nil {
1132+ return nil , err
1133+ }
1134+
1135+ if err := s .storage .Update (ctx , fqn , obj ); err != nil {
1136+ return nil , err
1137+ }
1138+
1139+ op := & pb.Operation {
1140+ TargetProject : name .Project .ID ,
1141+ OperationType : pb .Operation_SWITCHOVER ,
1142+ }
1143+
1144+ return s .operations .startLRO (ctx , op , obj , func () (proto.Message , error ) {
1145+ return obj , nil
1146+ })
1147+ }
1148+
9431149func (s * sqlInstancesService ) Delete (ctx context.Context , req * pb.SqlInstancesDeleteRequest ) (* pb.Operation , error ) {
9441150 name , err := s .buildInstanceName (req .GetProject (), req .GetInstance ())
9451151 if err != nil {
@@ -953,6 +1159,10 @@ func (s *sqlInstancesService) Delete(ctx context.Context, req *pb.SqlInstancesDe
9531159 return nil , err
9541160 }
9551161
1162+ if err := s .ensureMasterReflectsReplica (ctx , * name , deleted , nil ); err != nil {
1163+ return nil , fmt .Errorf ("ensuring master reflects replica: %w" , err )
1164+ }
1165+
9561166 op := & pb.Operation {
9571167 TargetProject : name .Project .ID ,
9581168 OperationType : pb .Operation_DELETE ,
0 commit comments