Skip to content

Commit fc32d25

Browse files
committed
DNS: introduce TXT support
1 parent 1dd122a commit fc32d25

2 files changed

Lines changed: 73 additions & 2 deletions

File tree

genesis_core/tests/functional/service/dns/test_pdns.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,3 +171,54 @@ def test_a_record(
171171
response = client.delete(url)
172172

173173
assert response.status_code == 204
174+
175+
def test_txt_record(
176+
self,
177+
user_api_client: iam_clients.GenesisCoreTestRESTClient,
178+
auth_user_admin: iam_clients.GenesisCoreAuth,
179+
domain1: tp.Dict,
180+
pdns_server: int | None,
181+
):
182+
client = user_api_client(auth_user_admin)
183+
184+
data = {
185+
"uuid": str(sys_uuid.uuid4()),
186+
"type": "TXT",
187+
"ttl": 0,
188+
"record": {"kind": "TXT", "name": "test", "content": "a" * 5000},
189+
}
190+
191+
url = client.build_collection_uri(
192+
["dns", "domains", domain1["uuid"], "records"]
193+
)
194+
195+
response = client.post(url, json=data)
196+
output = response.json()
197+
198+
assert response.status_code == 201
199+
assert self._cmp_shallow(data, output)
200+
201+
url = client.build_resource_uri(
202+
["dns", "domains", domain1["uuid"], "records", data["uuid"]]
203+
)
204+
205+
response = client.get(url)
206+
record = response.json()
207+
assert response.status_code == 200
208+
assert self._cmp_shallow(data, record)
209+
210+
if pdns_server:
211+
res = dns.resolver.make_resolver_at("127.0.0.1", port=pdns_server)
212+
answer = res.resolve(f"test.{DEF_DOMAIN}", "TXT")
213+
214+
assert len(answer) == 1
215+
# TXT records may not fit in one UDP frame, so there'll be many
216+
# strings inside
217+
assert (
218+
"".join([i.decode() for i in answer[0].strings]) == "a" * 5000
219+
)
220+
221+
# Delete
222+
response = client.delete(url)
223+
224+
assert response.status_code == 204

genesis_core/user_api/dns/dm/models.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
# License for the specific language governing permissions and limitations
1515
# under the License.
1616

17+
import re
18+
1719
from oslo_config import cfg
1820
from restalchemy.common import contexts
1921
from restalchemy.common import exceptions
@@ -112,7 +114,7 @@ def get_name(self, domain) -> str:
112114
)
113115

114116
def get_content(self, domain) -> str:
115-
return str(self.address)
117+
return str(self.content)
116118

117119

118120
class ARecord(AbstractRecord):
@@ -127,6 +129,9 @@ class ARecord(AbstractRecord):
127129
required=True,
128130
)
129131

132+
def get_content(self, domain) -> str:
133+
return str(self.address)
134+
130135

131136
class SOARecord(AbstractRecord):
132137
KIND = "SOA"
@@ -149,12 +154,26 @@ def get_content(self, domain) -> str:
149154
return f"{self.primary_dns} {domain.name} {self.serial} {self.refresh} {self.retry} {self.expire} {self.ttl}"
150155

151156

157+
class TXTRecord(AbstractRecord):
158+
KIND = "TXT"
159+
160+
name = properties.property(
161+
types_network.RecordName(),
162+
required=True,
163+
)
164+
content = properties.property(
165+
# Restrict newline and length
166+
types.BaseCompiledRegExpType(re.compile(r"^([^\n]{1,8192})$")),
167+
required=True,
168+
)
169+
170+
152171
class Record(CommonModel):
153172
__tablename__ = "dns_records"
154173
domain = relationships.relationship(Domain, required=True)
155174
domain_id = properties.property(types.Integer())
156175
type = properties.property(
157-
types.Enum(("A", "SOA")), # "AAAA", "CNAME", "MX", "TXT",
176+
types.Enum(("A", "SOA", "TXT")), # "AAAA", "CNAME", "MX",
158177
read_only=True,
159178
required=True,
160179
)
@@ -165,6 +184,7 @@ class Record(CommonModel):
165184
types_dynamic.KindModelSelectorType(
166185
types_dynamic.KindModelType(ARecord),
167186
types_dynamic.KindModelType(SOARecord),
187+
types_dynamic.KindModelType(TXTRecord),
168188
),
169189
required=True,
170190
)

0 commit comments

Comments
 (0)