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  }