Skip to content

Commit 6f01f63

Browse files
mircea-cosbucfealebenpae
authored andcommitted
CLOUDP-249012: Fix project retrieval when project prefixes overlap (#3720)
# Summary This patch fixes an error when the project look-up throws an error when an in-exact project name match was found, as illustrated in the modified E2E test: - Previously, if trying to create `projectA` and `projectABC` already exists, the project lookup would raise an error because the API would return `projectABC` and when looping over the response, no exact match for `projectA` would be found and instead of returning an empty project to be created, the operator would raise an error and `projectA` would fail to be created. ## Documentation changes * [x] Add an entry to [release notes](.../RELEASE_NOTES.md). * [ ] When needed, make sure you create a new [DOCSP ticket](https://jira.mongodb.org/projects/DOCSP) that documents your change. ## Changes to CRDs * [ ] Add `slaskawi`(Sebastian) and `@giohan` (George) as reviewers. * [ ] Make sure any changes are reflected on `/public/samples` directory.
1 parent e1f1718 commit 6f01f63

File tree

3 files changed

+96
-26
lines changed

3 files changed

+96
-26
lines changed

RELEASE_NOTES.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,12 @@
3636
to any other value than 1.
3737
* **MongoDB**, **AppDB**, **MongoDBMulti**: make sure to use external domains in the connectionString created if configured.
3838

39-
* **MongoDB**: remove panic when configuring shorter horizon config compared to number of members. Instead return a
40-
descriptive error.
39+
* **MongoDB**: Removed panic response when configuring shorter horizon config compared to number of members. The operator now signals a
40+
descriptive error in the status of the **MongoDB** resource.
41+
42+
* **MongoDB**: Fixed a bug where creating a resource in a new project named as a prefix of another project would fail, preventing the `MongoDB` resource to be created.
43+
44+
4145

4246
<!-- Past Releases -->
4347
# MongoDB Enterprise Kubernetes Operator 1.26.0

controllers/operator/project/project.go

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -145,41 +145,46 @@ func findProject(projectName string, organization *om.Organization, conn om.Conn
145145
return nil, nil
146146
}
147147

148+
// findProjectInsideOrganization looks up a project by name inside an organization and returns the project if it was the only one found by that name.
149+
// If no project was found, the function returns a nil project to indicate that no such project exists.
150+
// In all other cases, a non nil error is returned.
148151
func findProjectInsideOrganization(conn om.Connection, projectName string, organization *om.Organization, log *zap.SugaredLogger) (*om.Project, error) {
149-
// 1. Trying to find the project by name
150152
projects, err := conn.ReadProjectsInOrganizationByName(organization.ID, projectName)
151153
if err != nil {
152154
if v, ok := err.(*apierror.Error); ok {
155+
// If the project was not found, return an empty project and no error.
153156
if v.ErrorCode == apierror.ProjectNotFound {
154157
// ProjectNotFound is an expected condition.
158+
155159
return nil, nil
156160
}
157161
}
158-
log.Error(err)
162+
// Return an empty project and the OM api error in case there is a different API error.
163+
return nil, xerrors.Errorf("error looking up project %s in organization %s: %w", projectName, organization.ID, err)
159164
}
160165

161-
if err == nil {
162-
// there is no error so we need to check if the project found has this name
163-
// (the project found could be just the page of one single project if the OM is old and "name"
164-
// parameter is not supported)
165-
var projectsFound []*om.Project
166-
for _, project := range projects {
167-
if project.Name == projectName {
168-
projectsFound = append(projectsFound, project)
169-
}
166+
// There is no API error. We check if the project found has the exact name.
167+
// The API endpoint returns a list of projects and in case of no exact match, it would return the first item that matches the search term as a prefix.
168+
var projectsFound []*om.Project
169+
for _, project := range projects {
170+
if project.Name == projectName {
171+
projectsFound = append(projectsFound, project)
170172
}
173+
}
171174

172-
if len(projectsFound) == 1 {
173-
return projectsFound[0], nil
174-
} else if len(projectsFound) > 0 {
175-
projectsList := util.Transform(projectsFound, func(project *om.Project) string {
176-
return fmt.Sprintf("%s (%s)", project.Name, project.ID)
177-
})
178-
return nil, xerrors.Errorf("found more than one project with name %s in organization %s (%s): %v", projectName, organization.ID, organization.Name, strings.Join(projectsList, ", "))
179-
}
175+
if len(projectsFound) == 1 {
176+
// If there is just one project returned, and it matches the name, return it.
177+
return projectsFound[0], nil
178+
} else if len(projectsFound) > 0 {
179+
projectsList := util.Transform(projectsFound, func(project *om.Project) string {
180+
return fmt.Sprintf("%s (%s)", project.Name, project.ID)
181+
})
182+
// This should not happen, but older versions of OM supported the same name for a project in an org. We cannot proceed here so we return an error.
183+
return nil, xerrors.Errorf("found more than one project with name %s in organization %s (%s): %v", projectName, organization.ID, organization.Name, strings.Join(projectsList, ", "))
180184
}
181185

182-
return nil, xerrors.Errorf("could not find project %s in organization %s", projectName, organization.ID)
186+
// If there is no error from the API and no match in the response, return an empty project and no error.
187+
return nil, nil
183188
}
184189

185190
func findOrganizationByName(conn om.Connection, name string, log *zap.SugaredLogger) (string, error) {

docker/mongodb-enterprise-tests/tests/replicaset/replica_set_agent_flags.py

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
from typing import Optional
22

3-
from kubetester import create_or_update, find_fixture
3+
from kubetester import (
4+
create_or_update,
5+
create_or_update_configmap,
6+
find_fixture,
7+
random_k8s_name,
8+
read_configmap,
9+
)
410
from kubetester.kubetester import KubernetesTester
511
from kubetester.mongodb import MongoDB, Phase
612
from pytest import fixture, mark
@@ -16,10 +22,56 @@
1622

1723

1824
@fixture(scope="module")
19-
def replica_set(namespace: str, custom_mdb_version: str) -> MongoDB:
20-
resource = MongoDB.from_yaml(find_fixture("replica-set-basic.yaml"), namespace=namespace)
25+
def project_name_prefix() -> str:
26+
return random_k8s_name("project-prefix-")
27+
28+
29+
@fixture(scope="module")
30+
def first_project(namespace: str, project_name_prefix: str) -> str:
31+
cm = read_configmap(namespace=namespace, name="my-project")
32+
project_name = f"{project_name_prefix}-first"
33+
return create_or_update_configmap(
34+
namespace=namespace,
35+
name=project_name,
36+
data={
37+
"baseUrl": cm["baseUrl"],
38+
"projectName": project_name,
39+
"orgId": cm["orgId"],
40+
},
41+
)
42+
2143

44+
@fixture(scope="module")
45+
def second_project(namespace: str, project_name_prefix: str) -> str:
46+
cm = read_configmap(namespace=namespace, name="my-project")
47+
project_name = project_name_prefix
48+
return create_or_update_configmap(
49+
namespace=namespace,
50+
name=project_name,
51+
data={
52+
"baseUrl": cm["baseUrl"],
53+
"projectName": project_name,
54+
"orgId": cm["orgId"],
55+
},
56+
)
57+
58+
59+
@fixture(scope="module")
60+
def replica_set(namespace: str, first_project: str, custom_mdb_version: str) -> MongoDB:
61+
resource = MongoDB.from_yaml(find_fixture("replica-set-basic.yaml"), namespace=namespace, name="replica-set")
2262
resource.set_version(ensure_ent_version(custom_mdb_version))
63+
resource["spec"]["opsManager"]["configMapRef"]["name"] = first_project
64+
65+
create_or_update(resource)
66+
return resource
67+
68+
69+
@fixture(scope="module")
70+
def second_replica_set(namespace: str, second_project: str, custom_mdb_version: str) -> MongoDB:
71+
resource = MongoDB.from_yaml(find_fixture("replica-set-basic.yaml"), namespace=namespace, name="replica-set-2")
72+
resource.set_version(ensure_ent_version(custom_mdb_version))
73+
resource["spec"]["opsManager"]["configMapRef"]["name"] = second_project
74+
2375
create_or_update(resource)
2476
return resource
2577

@@ -29,6 +81,11 @@ def test_replica_set(replica_set: MongoDB):
2981
replica_set.assert_reaches_phase(Phase.Running, timeout=400)
3082

3183

84+
@mark.e2e_replica_set_agent_flags_and_readinessProbe
85+
def test_second_replica_set(second_replica_set: MongoDB):
86+
second_replica_set.assert_reaches_phase(Phase.Running, timeout=400)
87+
88+
3289
@mark.e2e_replica_set_agent_flags_and_readinessProbe
3390
def test_log_types_with_default_automation_log_file(replica_set: MongoDB):
3491
assert_pod_log_types(replica_set, get_all_default_log_types())
@@ -40,6 +97,10 @@ def test_set_custom_log_file(replica_set: MongoDB):
4097
replica_set["spec"]["agent"] = {
4198
"startupOptions": {
4299
"logFile": custom_agent_log_path,
100+
"maxLogFileSize": "10485760",
101+
"maxLogFiles": "5",
102+
"maxLogFileDurationHrs": "16",
103+
"logFile": "/var/log/mongodb-mms-automation/customLogFile",
43104
}
44105
}
45106
replica_set["spec"]["agent"].setdefault("readinessProbe", {})
@@ -101,7 +162,7 @@ def test_enable_audit_log(replica_set: MongoDB):
101162
replica_set["spec"]["additionalMongodConfig"] = additional_mongod_config
102163
create_or_update(replica_set)
103164

104-
replica_set.assert_reaches_phase(Phase.Running, timeout=400)
165+
replica_set.assert_reaches_phase(Phase.Running, timeout=600)
105166

106167

107168
@mark.e2e_replica_set_agent_flags_and_readinessProbe

0 commit comments

Comments
 (0)