github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/cloudservice.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "github.com/juju/errors" 8 "github.com/juju/mgo/v3" 9 "github.com/juju/mgo/v3/bson" 10 "github.com/juju/mgo/v3/txn" 11 12 "github.com/juju/juju/core/network" 13 ) 14 15 // CloudServicer represents the state of a CAAS service. 16 type CloudServicer interface { 17 // ProviderId returns the id assigned to the service 18 // by the cloud. 19 ProviderId() string 20 21 // Addresses returns the service addresses. 22 Addresses() network.SpaceAddresses 23 24 // Generation returns the service config generation. 25 Generation() int64 26 27 // DesiredScaleProtected indicates if current desired scale in application has been applied to the cluster. 28 DesiredScaleProtected() bool 29 } 30 31 // CloudService is an implementation of CloudService. 32 type CloudService struct { 33 st *State 34 doc cloudServiceDoc 35 } 36 37 type cloudServiceDoc struct { 38 // DocID holds cloud service document key. 39 DocID string `bson:"_id"` 40 41 ProviderId string `bson:"provider-id"` 42 Addresses []address `bson:"addresses"` 43 44 // Generation is the version of current service configuration. 45 // It prevents the scale updated to replicas of the older/previous generations of deployment/statefulset. 46 // Currently only DesiredScale is versioned. 47 Generation int64 `bson:"generation"` 48 49 // DesiredScaleProtected indicates if the desired scale needs to be applied to k8s cluster. 50 // It prevents the desired scale requested from CLI by user incidentally updated by 51 // k8s cluster replicas before having a chance to be applied/deployed. 52 DesiredScaleProtected bool `bson:"desired-scale-protected"` 53 } 54 55 func newCloudService(st *State, doc *cloudServiceDoc) *CloudService { 56 svc := &CloudService{ 57 st: st, 58 doc: *doc, 59 } 60 return svc 61 } 62 63 // Id implements CloudServicer. 64 func (c *CloudService) Id() string { 65 return c.st.localID(c.doc.DocID) 66 } 67 68 // ProviderId implements CloudServicer. 69 func (c *CloudService) ProviderId() string { 70 return c.doc.ProviderId 71 } 72 73 // Addresses implements CloudServicer. 74 func (c *CloudService) Addresses() network.SpaceAddresses { 75 return networkAddresses(c.doc.Addresses) 76 } 77 78 // Generation implements CloudServicer. 79 func (c *CloudService) Generation() int64 { 80 return c.doc.Generation 81 } 82 83 // DesiredScaleProtected implements CloudServicer. 84 func (c *CloudService) DesiredScaleProtected() bool { 85 return c.doc.DesiredScaleProtected 86 } 87 88 func (c *CloudService) cloudServiceDoc() (*cloudServiceDoc, error) { 89 coll, closer := c.st.db().GetCollection(cloudServicesC) 90 defer closer() 91 92 var doc cloudServiceDoc 93 err := coll.FindId(c.doc.DocID).One(&doc) 94 if err == mgo.ErrNotFound { 95 return nil, errors.NotFoundf("cloud service %v", c.Id()) 96 } 97 if err != nil { 98 return nil, errors.Trace(err) 99 } 100 return &doc, nil 101 } 102 103 // CloudService return the content of cloud service from the underlying state. 104 // It returns an error that satisfies errors.IsNotFound if the cloud service has been removed. 105 func (c *CloudService) CloudService() (*CloudService, error) { 106 doc, err := c.cloudServiceDoc() 107 if err != nil { 108 return nil, errors.Trace(err) 109 } 110 if doc == nil { 111 return nil, errors.NotFoundf("cloud service %v", c.Id()) 112 } 113 c.doc = *doc 114 return c, nil 115 } 116 117 // Refresh refreshes the content of cloud service from the underlying state. 118 // It returns an error that satisfies errors.IsNotFound if the cloud service has been removed. 119 func (c *CloudService) Refresh() error { 120 _, err := c.CloudService() 121 return errors.Trace(err) 122 } 123 124 func buildCloudServiceOps(st *State, doc cloudServiceDoc) ([]txn.Op, error) { 125 svc := newCloudService(st, &doc) 126 existing, err := svc.cloudServiceDoc() 127 if err != nil && !errors.IsNotFound(err) { 128 return nil, errors.Trace(err) 129 } 130 if err != nil || existing == nil { 131 return []txn.Op{{ 132 C: cloudServicesC, 133 Id: doc.DocID, 134 Assert: txn.DocMissing, 135 Insert: doc, 136 }}, nil 137 } 138 patchFields := bson.D{} 139 addField := func(elm bson.DocElem) { 140 patchFields = append(patchFields, elm) 141 } 142 if doc.ProviderId != "" { 143 addField(bson.DocElem{"provider-id", doc.ProviderId}) 144 } 145 if len(doc.Addresses) > 0 { 146 addField(bson.DocElem{"addresses", doc.Addresses}) 147 } 148 if doc.Generation > existing.Generation { 149 addField(bson.DocElem{"generation", doc.Generation}) 150 } 151 if doc.DesiredScaleProtected != existing.DesiredScaleProtected { 152 addField(bson.DocElem{"desired-scale-protected", doc.DesiredScaleProtected}) 153 } 154 return []txn.Op{{ 155 C: cloudServicesC, 156 Id: existing.DocID, 157 Assert: bson.D{{"$or", []bson.D{ 158 {{"provider-id", existing.ProviderId}}, 159 {{"provider-id", bson.D{{"$exists", false}}}}, 160 }}}, 161 Update: bson.D{ 162 {"$set", patchFields}, 163 }, 164 }}, nil 165 } 166 167 func (a *Application) removeCloudServiceOps() []txn.Op { 168 ops := []txn.Op{{ 169 C: cloudServicesC, 170 Id: a.globalKey(), 171 Remove: true, 172 }} 173 return ops 174 }