mongo-charms-single-kernel 1.8.6__py3-none-any.whl → 1.8.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of mongo-charms-single-kernel might be problematic. Click here for more details.

Files changed (44) hide show
  1. {mongo_charms_single_kernel-1.8.6.dist-info → mongo_charms_single_kernel-1.8.7.dist-info}/METADATA +2 -1
  2. {mongo_charms_single_kernel-1.8.6.dist-info → mongo_charms_single_kernel-1.8.7.dist-info}/RECORD +38 -39
  3. single_kernel_mongo/abstract_charm.py +8 -0
  4. single_kernel_mongo/config/literals.py +1 -20
  5. single_kernel_mongo/config/relations.py +0 -1
  6. single_kernel_mongo/config/statuses.py +10 -57
  7. single_kernel_mongo/core/abstract_upgrades_v3.py +149 -0
  8. single_kernel_mongo/core/k8s_workload.py +2 -2
  9. single_kernel_mongo/core/kubernetes_upgrades_v3.py +17 -0
  10. single_kernel_mongo/core/machine_upgrades_v3.py +54 -0
  11. single_kernel_mongo/core/operator.py +25 -4
  12. single_kernel_mongo/core/version_checker.py +7 -6
  13. single_kernel_mongo/core/vm_workload.py +30 -13
  14. single_kernel_mongo/core/workload.py +17 -19
  15. single_kernel_mongo/events/backups.py +3 -3
  16. single_kernel_mongo/events/cluster.py +1 -1
  17. single_kernel_mongo/events/database.py +1 -1
  18. single_kernel_mongo/events/lifecycle.py +5 -4
  19. single_kernel_mongo/events/tls.py +7 -4
  20. single_kernel_mongo/exceptions.py +4 -24
  21. single_kernel_mongo/managers/cluster.py +8 -8
  22. single_kernel_mongo/managers/config.py +5 -3
  23. single_kernel_mongo/managers/ldap.py +2 -1
  24. single_kernel_mongo/managers/mongo.py +48 -9
  25. single_kernel_mongo/managers/mongodb_operator.py +195 -67
  26. single_kernel_mongo/managers/mongos_operator.py +95 -35
  27. single_kernel_mongo/managers/sharding.py +4 -4
  28. single_kernel_mongo/managers/tls.py +54 -27
  29. single_kernel_mongo/managers/upgrade_v3.py +452 -0
  30. single_kernel_mongo/managers/upgrade_v3_status.py +133 -0
  31. single_kernel_mongo/state/app_peer_state.py +12 -2
  32. single_kernel_mongo/state/charm_state.py +31 -141
  33. single_kernel_mongo/state/config_server_state.py +0 -33
  34. single_kernel_mongo/state/unit_peer_state.py +10 -0
  35. single_kernel_mongo/utils/helpers.py +0 -6
  36. single_kernel_mongo/utils/mongo_config.py +32 -8
  37. single_kernel_mongo/core/abstract_upgrades.py +0 -890
  38. single_kernel_mongo/core/kubernetes_upgrades.py +0 -194
  39. single_kernel_mongo/core/machine_upgrades.py +0 -188
  40. single_kernel_mongo/events/upgrades.py +0 -157
  41. single_kernel_mongo/managers/upgrade.py +0 -334
  42. single_kernel_mongo/state/upgrade_state.py +0 -134
  43. {mongo_charms_single_kernel-1.8.6.dist-info → mongo_charms_single_kernel-1.8.7.dist-info}/WHEEL +0 -0
  44. {mongo_charms_single_kernel-1.8.6.dist-info → mongo_charms_single_kernel-1.8.7.dist-info}/licenses/LICENSE +0 -0
@@ -23,7 +23,6 @@ from pymongo.errors import (
23
23
 
24
24
  from single_kernel_mongo.config.literals import (
25
25
  SECRETS_UNIT,
26
- SNAP,
27
26
  CharmKind,
28
27
  MongoPorts,
29
28
  Scope,
@@ -67,13 +66,8 @@ from single_kernel_mongo.state.tls_state import TLSState
67
66
  from single_kernel_mongo.state.unit_peer_state import (
68
67
  UnitPeerReplicaSet,
69
68
  )
70
- from single_kernel_mongo.state.upgrade_state import (
71
- AppUpgradePeerData,
72
- UnitUpgradePeerData,
73
- )
74
69
  from single_kernel_mongo.utils.helpers import (
75
70
  generate_relation_departed_key,
76
- unit_number,
77
71
  )
78
72
  from single_kernel_mongo.utils.mongo_config import MongoConfiguration
79
73
  from single_kernel_mongo.utils.mongo_connection import MongoConnection
@@ -143,12 +137,6 @@ class CharmState(Object, StatusesStateProtocol):
143
137
  self.ldap_peer_relation_name,
144
138
  )
145
139
 
146
- self.upgrade_app_interface = DataPeerData(
147
- self.model, relation_name=RelationNames.UPGRADE_VERSION.value
148
- )
149
- self.upgrade_unit_interface = DataPeerUnitData(
150
- self.model, relation_name=RelationNames.UPGRADE_VERSION.value
151
- )
152
140
  self.paths = MongoPaths(self.charm_role)
153
141
 
154
142
  self.k8s_manager = K8sManager(
@@ -184,6 +172,13 @@ class CharmState(Object, StatusesStateProtocol):
184
172
  return set()
185
173
  return self.peer_relation.units
186
174
 
175
+ @property
176
+ def reverse_order_peer_units(self) -> list[Unit]:
177
+ """Units sorted in reverse order."""
178
+ return sorted(
179
+ self.peers_units, key=lambda unit: int(unit.name.split("/")[-1]), reverse=True
180
+ )
181
+
187
182
  @property
188
183
  def client_relations(self) -> set[Relation]:
189
184
  """The set of client relations.
@@ -196,11 +191,6 @@ class CharmState(Object, StatusesStateProtocol):
196
191
  return set(self.model.relations[RelationNames.MONGOS_PROXY.value])
197
192
  return set(self.model.relations[RelationNames.DATABASE.value])
198
193
 
199
- @property
200
- def upgrade_relation(self) -> Relation | None:
201
- """The set of upgrade relations."""
202
- return self.model.get_relation(RelationNames.UPGRADE_VERSION.value)
203
-
204
194
  @property
205
195
  def mongos_cluster_relation(self) -> Relation | None:
206
196
  """The Mongos side of the cluster relation."""
@@ -283,48 +273,6 @@ class CharmState(Object, StatusesStateProtocol):
283
273
  k8s_manager=self.k8s_manager,
284
274
  )
285
275
 
286
- @property
287
- def unit_upgrade_peer_data(self) -> UnitUpgradePeerData:
288
- """This unit upgrade databag."""
289
- return UnitUpgradePeerData(
290
- relation=self.upgrade_relation,
291
- data_interface=self.upgrade_unit_interface,
292
- component=self.model.unit,
293
- substrate=self.substrate,
294
- )
295
-
296
- @property
297
- def app_upgrade_peer_data(self) -> AppUpgradePeerData:
298
- """The app upgrade databag."""
299
- return AppUpgradePeerData(
300
- relation=self.upgrade_relation,
301
- data_interface=self.upgrade_app_interface,
302
- component=self.model.app,
303
- substrate=self.substrate,
304
- )
305
-
306
- @property
307
- def units_upgrade_peer_data(self) -> list[UnitUpgradePeerData]:
308
- """Grabs all units in the current peer relation, including this unit.
309
-
310
- Returns:
311
- Sorted list of UnitUpgradePeerData in the current upgrade relation,
312
- including this unit.
313
- """
314
- _units = []
315
- for unit, data_interface in self.upgrade_units_data_interfaces.items():
316
- _units.append(
317
- UnitUpgradePeerData(
318
- relation=self.upgrade_relation,
319
- data_interface=data_interface,
320
- component=unit,
321
- substrate=self.substrate,
322
- )
323
- )
324
- _units.append(self.unit_upgrade_peer_data)
325
-
326
- return sorted(_units, key=unit_number, reverse=True)
327
-
328
276
  @property
329
277
  def units(self) -> set[UnitPeerReplicaSet]:
330
278
  """Grabs all units in the current peer relation, including this unit.
@@ -432,63 +380,6 @@ class CharmState(Object, StatusesStateProtocol):
432
380
  def db_initialised(self, other: bool):
433
381
  self.app_peer_data.db_initialised = other
434
382
 
435
- @property
436
- def unit_workload_container_versions(self) -> dict[str, str]:
437
- """{Unit name: unique identifier for unit's workload container version}.
438
-
439
- If and only if this version changes, the workload will restart (during upgrade or
440
- rollback).
441
-
442
- On Kubernetes, the workload & charm are upgraded together
443
- On machines, the charm is upgraded before the workload
444
-
445
- VM: container version is the snap revision.
446
- K8S: container version is the stateful set hash.
447
-
448
- This identifier should be comparable to `_app_workload_container_version` to determine if
449
- the unit & app are the same workload container version.
450
- """
451
- if self.substrate == Substrates.K8S:
452
- return self.k8s_manager.list_revisions()
453
- return {
454
- unit.name: unit.snap_revision
455
- for unit in self.units_upgrade_peer_data
456
- if unit.snap_revision
457
- }
458
-
459
- @property
460
- def unit_workload_container_version(self) -> str | None:
461
- """Installed snap revision for this unit."""
462
- if self.substrate == Substrates.K8S:
463
- return self.k8s_manager.list_revisions().get(self.model.unit.name)
464
- return self.unit_upgrade_peer_data.snap_revision
465
-
466
- @unit_workload_container_version.setter
467
- def unit_workload_container_version(self, value: str):
468
- if self.substrate == Substrates.VM:
469
- self.unit_upgrade_peer_data.snap_revision = value
470
-
471
- @property
472
- def app_workload_container_version(self) -> str:
473
- """Unique identifier for the app's workload container version.
474
-
475
- This should match the workload version in the current Juju app charm version.
476
-
477
- This identifier should be comparable to `_unit_workload_container_versions` to determine if
478
- the app & unit are the same workload container version.
479
- """
480
- if self.substrate == Substrates.K8S:
481
- return self.k8s_manager.get_revision()
482
- return SNAP.revision
483
-
484
- @property
485
- def upgrade_in_progress(self) -> bool:
486
- """Is the charm in upgrade?"""
487
- app_version = self.app_workload_container_version
488
- unit_versions = self.unit_workload_container_versions
489
- logger.debug(f"Upgrade in progress check: {app_version = } / {unit_versions = }")
490
- return any(version != app_version for version in unit_versions.values())
491
-
492
383
  @property
493
384
  def bind_address(self) -> IPv4Address | IPv6Address | str:
494
385
  """The network binding address from the peer relation."""
@@ -541,18 +432,6 @@ class CharmState(Object, StatusesStateProtocol):
541
432
  for unit in self.peers_units
542
433
  }
543
434
 
544
- @property
545
- def upgrade_units_data_interfaces(self) -> dict[Unit, DataPeerOtherUnitData]:
546
- """The cluster peer relation."""
547
- return {
548
- unit: DataPeerOtherUnitData(
549
- model=self.model,
550
- unit=unit,
551
- relation_name=RelationNames.UPGRADE_VERSION.value,
552
- )
553
- for unit in self.peers_units
554
- }
555
-
556
435
  @property
557
436
  def formatted_socket_path(self) -> str:
558
437
  """URL encoded socket path.
@@ -657,6 +536,17 @@ class CharmState(Object, StatusesStateProtocol):
657
536
  )
658
537
  return None
659
538
 
539
+ def get_subject_name(self) -> str:
540
+ """Generate the subject name for CSR."""
541
+ # In sharded MongoDB deployments it is a requirement that all subject names match across
542
+ # all cluster components. The config-server name is the source of truth across mongos and
543
+ # shard deployments.
544
+ if self.is_role(MongoDBRoles.REPLICATION) or self.is_role(MongoDBRoles.CONFIG_SERVER):
545
+ return self.model.app.name
546
+ # until integrated with config-server use current app name as
547
+ # subject name
548
+ return self.config_server_name or self.model.app.name
549
+
660
550
  def generate_config_server_db(self) -> str:
661
551
  """Generates the config server DB URI."""
662
552
  replica_set_name = self.model.app.name
@@ -709,19 +599,16 @@ class CharmState(Object, StatusesStateProtocol):
709
599
  self.unit_peer_data.update({rel_departed_key: json.dumps(scaling_down)})
710
600
  return scaling_down
711
601
 
712
- @property
713
- def upgrade_resumed(self) -> bool:
714
- """Whether the user has resumed upgrade with juju action."""
715
- if self.substrate == Substrates.K8S:
716
- return self.k8s_manager.get_partition() < unit_number(self.units_upgrade_peer_data[0])
717
- return self.app_upgrade_peer_data.upgrade_resumed
718
-
719
602
  def is_shard_added_to_cluster(self) -> bool:
720
603
  """Returns true if the shard has been added to the clusted."""
721
604
  # this information is required in order to check if we have been added
722
605
  if not self.config_server_name or not self.app_peer_data.mongos_hosts:
723
606
  return False
724
607
 
608
+ # We can't check if we don't have a valid certificate
609
+ if self.shard_state.internal_ca_secret is not None and not self.tls.external_enabled:
610
+ return False
611
+
725
612
  try:
726
613
  # check our ability to use connect to mongos
727
614
  with MongoConnection(self.remote_mongos_config) as mongos:
@@ -778,8 +665,9 @@ class CharmState(Object, StatusesStateProtocol):
778
665
  hosts=hosts or user.hosts,
779
666
  port=MongoPorts.MONGODB_PORT.value,
780
667
  roles=user.roles,
781
- tls_external=self.tls.external_enabled,
782
- tls_internal=self.tls.internal_enabled,
668
+ tls_enabled=self.tls.external_enabled,
669
+ tls_external_keyfile=self.paths.ext_pem_file,
670
+ tls_external_ca=self.paths.ext_ca_file,
783
671
  standalone=standalone,
784
672
  )
785
673
 
@@ -807,8 +695,9 @@ class CharmState(Object, StatusesStateProtocol):
807
695
  hosts=hosts or user.hosts,
808
696
  port=MongoPorts.MONGOS_PORT.value,
809
697
  roles=user.roles,
810
- tls_external=self.tls.external_enabled,
811
- tls_internal=self.tls.internal_enabled,
698
+ tls_enabled=self.tls.external_enabled,
699
+ tls_external_keyfile=self.paths.ext_pem_file,
700
+ tls_external_ca=self.paths.ext_ca_file,
812
701
  )
813
702
 
814
703
  @property
@@ -862,8 +751,9 @@ class CharmState(Object, StatusesStateProtocol):
862
751
  # unlike the vm mongos charm, the K8s charm does not communicate with the unix socket
863
752
  port=port,
864
753
  roles={RoleNames.ADMIN},
865
- tls_external=self.tls.external_enabled,
866
- tls_internal=self.tls.internal_enabled,
754
+ tls_enabled=self.tls.external_enabled,
755
+ tls_external_keyfile=self.paths.ext_pem_file,
756
+ tls_external_ca=self.paths.ext_ca_file,
867
757
  )
868
758
 
869
759
  @property
@@ -39,12 +39,6 @@ SECRETS_FIELDS = [
39
39
  ]
40
40
 
41
41
 
42
- class UnitShardingComponentKeys(str, Enum):
43
- """Config Server State Model for the unit."""
44
-
45
- STATUS_READY_FOR_UPGRADE = "status-shows-ready-for-upgrade"
46
-
47
-
48
42
  class AppShardingComponentState(AbstractRelationState[Data]):
49
43
  """The stored state for the ConfigServer Relation."""
50
44
 
@@ -120,30 +114,3 @@ class UnitShardingComponentState(AbstractRelationState[Data]):
120
114
  def __init__(self, relation: Relation | None, data_interface: Data, component: Unit):
121
115
  super().__init__(relation, data_interface=data_interface, component=component)
122
116
  self.data_interface = data_interface
123
-
124
- @property
125
- def status_ready_for_upgrade(self) -> bool:
126
- """Returns true if the shard is ready for upgrade.
127
-
128
- Legacy: for old upgrades that require status from shards.
129
- """
130
- if not self.relation:
131
- return True
132
- # We get directly the data in the unit because it's hidden otherwise
133
- return json.loads(
134
- self.relation.data[self.component].get(
135
- UnitShardingComponentKeys.STATUS_READY_FOR_UPGRADE.value, "false"
136
- )
137
- )
138
-
139
- @status_ready_for_upgrade.setter
140
- def status_ready_for_upgrade(self, value: bool):
141
- """Sets old status.
142
-
143
- Legacy: for old upgrades that require status from shards.
144
- """
145
- if not self.relation:
146
- raise Exception("No databag to write in")
147
- self.relation.data[self.component][
148
- UnitShardingComponentKeys.STATUS_READY_FOR_UPGRADE.value
149
- ] = json.dumps(value)
@@ -23,6 +23,7 @@ class UnitPeerRelationKeys(str, Enum):
23
23
  INGRESS_ADDRESS = "ingress-address"
24
24
  EGRESS_SUBNETS = "egress-subnets"
25
25
  DRAINED = "drained"
26
+ CURRENT_REVISION = "current_revision"
26
27
 
27
28
 
28
29
  class UnitPeerReplicaSet(AbstractRelationState[DataPeerUnitData]):
@@ -112,3 +113,12 @@ class UnitPeerReplicaSet(AbstractRelationState[DataPeerUnitData]):
112
113
  if not isinstance(value, bool):
113
114
  raise ValueError(f"drained value is not boolean but {value}")
114
115
  self.update({UnitPeerRelationKeys.DRAINED.value: json.dumps(value)})
116
+
117
+ @property
118
+ def current_revision(self) -> str:
119
+ """The revision of the charm that's running before the upgrade."""
120
+ return self.relation_data.get(UnitPeerRelationKeys.CURRENT_REVISION, "-1")
121
+
122
+ @current_revision.setter
123
+ def current_revision(self, value: str):
124
+ self.update({UnitPeerRelationKeys.CURRENT_REVISION.value: value})
@@ -14,7 +14,6 @@ from pydantic import ValidationError
14
14
  from single_kernel_mongo.config.literals import CharmKind, Substrates
15
15
  from single_kernel_mongo.core.structured_config import LdapUserToDnMapping
16
16
  from single_kernel_mongo.exceptions import InvalidCharmKindError
17
- from single_kernel_mongo.state.upgrade_state import UnitUpgradePeerData
18
17
 
19
18
  logger = getLogger(__name__)
20
19
 
@@ -62,11 +61,6 @@ def hostname_from_shardname(host: str) -> str:
62
61
  return host.split("/")[0]
63
62
 
64
63
 
65
- def unit_number(unit: UnitUpgradePeerData) -> int:
66
- """Gets the unit number from a unit upgrade peer data."""
67
- return int(unit.component.name.split("/")[-1])
68
-
69
-
70
64
  def is_valid_ldapusertodnmapping(ldap_user_to_dn_mapping: str) -> bool:
71
65
  """Validates the mapping, returning a boolean."""
72
66
  if not ldap_user_to_dn_mapping:
@@ -4,6 +4,7 @@
4
4
 
5
5
  from dataclasses import dataclass
6
6
  from itertools import chain
7
+ from pathlib import Path
7
8
  from urllib.parse import quote_plus, urlencode
8
9
 
9
10
  from single_kernel_mongo.config.literals import MongoPorts
@@ -35,8 +36,9 @@ class MongoConfiguration:
35
36
  password: str
36
37
  hosts: set[str]
37
38
  roles: set[str]
38
- tls_external: bool
39
- tls_internal: bool
39
+ tls_enabled: bool
40
+ tls_external_keyfile: Path = Path("")
41
+ tls_external_ca: Path = Path("")
40
42
  port: int | None = None
41
43
  replset: str | None = None
42
44
  standalone: bool = False
@@ -63,27 +65,38 @@ class MongoConfiguration:
63
65
  return {}
64
66
 
65
67
  @property
66
- def uri(self) -> str:
67
- """Return URI concatenated from fields."""
68
+ def tls_config(self) -> dict:
69
+ """TLS Config."""
70
+ if not self.tls_enabled:
71
+ return {}
72
+ return {
73
+ "tls": "true",
74
+ "tlsCertificateKeyFile": f"{self.tls_external_keyfile}",
75
+ "tlsCaFile": f"{self.tls_external_ca}",
76
+ }
77
+
78
+ def _uri(self, tls: bool):
68
79
  if self.port == MongoPorts.MONGOS_PORT and self.replset:
69
80
  raise AmbiguousConfigError("Mongos cannot support replica set")
70
81
 
71
82
  if self.standalone and not self.port:
72
83
  raise AmbiguousConfigError("Standalone connection needs a port")
73
84
 
85
+ tls_config = self.tls_config if tls else {}
86
+ auth_source = self.formatted_auth_source
87
+
74
88
  if self.standalone:
75
89
  return (
76
90
  f"mongodb://{quote_plus(self.username)}:"
77
91
  f"{quote_plus(self.password)}@"
78
- f"localhost:{self.port}/?authSource=admin"
92
+ f"localhost:{self.port}/?{urlencode(auth_source | tls_config)}"
79
93
  )
80
94
 
81
95
  complete_hosts = ",".join(sorted(self.formatted_hosts))
82
96
  replset = self.formatted_replset
83
- auth_source = self.formatted_auth_source
84
97
 
85
98
  # Dict of all parameters.
86
- parameters = replset | auth_source
99
+ parameters = replset | auth_source | tls_config
87
100
 
88
101
  return (
89
102
  f"mongodb://{quote_plus(self.username)}:"
@@ -92,6 +105,16 @@ class MongoConfiguration:
92
105
  f"{urlencode(parameters)}"
93
106
  )
94
107
 
108
+ @property
109
+ def uri(self) -> str:
110
+ """Return URI concatenated from fields."""
111
+ return self._uri(tls=True)
112
+
113
+ @property
114
+ def uri_without_tls(self) -> str:
115
+ """Return URI concatenated from fields without tls params."""
116
+ return self._uri(tls=False)
117
+
95
118
  @property
96
119
  def supported_roles(self) -> list[DBPrivilege]:
97
120
  """The supported roles for this configuration."""
@@ -112,5 +135,6 @@ EMPTY_CONFIGURATION = MongoConfiguration(
112
135
  set(),
113
136
  set(),
114
137
  False,
115
- False,
138
+ Path(""),
139
+ Path(""),
116
140
  )