OpenStack DNS As A Service (Designate)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

service.py 101KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891
  1. # Copyright 2012 Managed I.T.
  2. # Copyright 2013 - 2014 Hewlett-Packard Development Company, L.P.
  3. #
  4. # Author: Kiall Mac Innes <kiall@managedit.ie>
  5. #
  6. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  7. # not use this file except in compliance with the License. You may obtain
  8. # a copy of the License at
  9. #
  10. # http://www.apache.org/licenses/LICENSE-2.0
  11. #
  12. # Unless required by applicable law or agreed to in writing, software
  13. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  14. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  15. # License for the specific language governing permissions and limitations
  16. # under the License.
  17. import re
  18. import collections
  19. import copy
  20. import functools
  21. import threading
  22. import itertools
  23. import string
  24. import signal
  25. import random
  26. import time
  27. import six
  28. from eventlet import tpool
  29. from dns import zone as dnszone
  30. from dns import exception as dnsexception
  31. from oslo_config import cfg
  32. import oslo_messaging as messaging
  33. from oslo_log import log as logging
  34. from oslo_concurrency import lockutils
  35. from designate.i18n import _LI
  36. from designate.i18n import _LC
  37. from designate.i18n import _LE
  38. from designate.i18n import _LW
  39. from designate import context as dcontext
  40. from designate import exceptions
  41. from designate import dnsutils
  42. from designate import network_api
  43. from designate import notifications
  44. from designate import objects
  45. from designate import policy
  46. from designate import quota
  47. from designate import service
  48. from designate import scheduler
  49. from designate import utils
  50. from designate import storage
  51. from designate.mdns import rpcapi as mdns_rpcapi
  52. from designate.pool_manager import rpcapi as pool_manager_rpcapi
  53. from designate.storage import transaction
  54. from designate.worker import rpcapi as worker_rpcapi
  55. LOG = logging.getLogger(__name__)
  56. ZONE_LOCKS = threading.local()
  57. NOTIFICATION_BUFFER = threading.local()
  58. def synchronized_zone(zone_arg=1, new_zone=False):
  59. """Ensures only a single operation is in progress for each zone
  60. A Decorator which ensures only a single operation can be happening
  61. on a single zone at once, within the current designate-central instance
  62. """
  63. def outer(f):
  64. @functools.wraps(f)
  65. def sync_wrapper(self, *args, **kwargs):
  66. if not hasattr(ZONE_LOCKS, 'held'):
  67. # Create the held set if necessary
  68. ZONE_LOCKS.held = set()
  69. zone_id = None
  70. if 'zone_id' in kwargs:
  71. zone_id = kwargs['zone_id']
  72. elif 'zone' in kwargs:
  73. zone_id = kwargs['zone'].id
  74. elif 'recordset' in kwargs:
  75. zone_id = kwargs['recordset'].zone_id
  76. elif 'record' in kwargs:
  77. zone_id = kwargs['record'].zone_id
  78. # The various objects won't always have an ID set, we should
  79. # attempt to locate an Object containing the ID.
  80. if zone_id is None:
  81. for arg in itertools.chain(kwargs.values(), args):
  82. if isinstance(arg, objects.Zone):
  83. zone_id = arg.id
  84. if zone_id is not None:
  85. break
  86. elif (isinstance(arg, objects.RecordSet) or
  87. isinstance(arg, objects.Record) or
  88. isinstance(arg, objects.ZoneTransferRequest) or
  89. isinstance(arg, objects.ZoneTransferAccept)):
  90. zone_id = arg.zone_id
  91. if zone_id is not None:
  92. break
  93. # If we still don't have an ID, find the Nth argument as
  94. # defined by the zone_arg decorator option.
  95. if zone_id is None and len(args) > zone_arg:
  96. zone_id = args[zone_arg]
  97. if isinstance(zone_id, objects.Zone):
  98. # If the value is a Zone object, extract it's ID.
  99. zone_id = zone_id.id
  100. if not new_zone and zone_id is None:
  101. raise Exception('Failed to determine zone id for '
  102. 'synchronized operation')
  103. if zone_id in ZONE_LOCKS.held:
  104. # Call the wrapped function
  105. return f(self, *args, **kwargs)
  106. else:
  107. with lockutils.lock('zone-%s' % zone_id):
  108. ZONE_LOCKS.held.add(zone_id)
  109. # Call the wrapped function
  110. result = f(self, *args, **kwargs)
  111. ZONE_LOCKS.held.remove(zone_id)
  112. return result
  113. sync_wrapper.__wrapped_function = f
  114. sync_wrapper.__wrapper_name = 'synchronized_zone'
  115. return sync_wrapper
  116. return outer
  117. def notification(notification_type):
  118. def outer(f):
  119. @functools.wraps(f)
  120. def notification_wrapper(self, *args, **kwargs):
  121. if not hasattr(NOTIFICATION_BUFFER, 'queue'):
  122. # Create the notifications queue if necessary
  123. NOTIFICATION_BUFFER.stack = 0
  124. NOTIFICATION_BUFFER.queue = collections.deque()
  125. NOTIFICATION_BUFFER.stack += 1
  126. try:
  127. # Find the context argument
  128. context = dcontext.DesignateContext.\
  129. get_context_from_function_and_args(f, args, kwargs)
  130. # Call the wrapped function
  131. result = f(self, *args, **kwargs)
  132. # Feed the args/result to a notification plugin
  133. # to determine what is emitted
  134. payloads = notifications.get_plugin().emit(
  135. notification_type, context, result, args, kwargs)
  136. # Enqueue the notification
  137. for payload in payloads:
  138. LOG.debug('Queueing notification for %(type)s ',
  139. {'type': notification_type})
  140. NOTIFICATION_BUFFER.queue.appendleft(
  141. (context, notification_type, payload,))
  142. return result
  143. finally:
  144. NOTIFICATION_BUFFER.stack -= 1
  145. if NOTIFICATION_BUFFER.stack == 0:
  146. LOG.debug('Emitting %(count)d notifications',
  147. {'count': len(NOTIFICATION_BUFFER.queue)})
  148. # Send the queued notifications, in order.
  149. for value in NOTIFICATION_BUFFER.queue:
  150. LOG.debug('Emitting %(type)s notification',
  151. {'type': value[1]})
  152. self.notifier.info(value[0], value[1], value[2])
  153. # Reset the queue
  154. NOTIFICATION_BUFFER.queue.clear()
  155. return notification_wrapper
  156. return outer
  157. class Service(service.RPCService, service.Service):
  158. RPC_API_VERSION = '6.2'
  159. target = messaging.Target(version=RPC_API_VERSION)
  160. def __init__(self, threads=None):
  161. super(Service, self).__init__(threads=threads)
  162. self.network_api = network_api.get_network_api(cfg.CONF.network_api)
  163. # update_service_status needs is called by the emitter so we pass
  164. # ourselves as the rpc_api.
  165. self.heartbeat_emitter.rpc_api = self
  166. @property
  167. def scheduler(self):
  168. if not hasattr(self, '_scheduler'):
  169. # Get a scheduler instance
  170. self._scheduler = scheduler.get_scheduler(storage=self.storage)
  171. return self._scheduler
  172. @property
  173. def quota(self):
  174. if not hasattr(self, '_quota'):
  175. # Get a quota manager instance
  176. self._quota = quota.get_quota()
  177. return self._quota
  178. @property
  179. def storage(self):
  180. if not hasattr(self, '_storage'):
  181. # Get a storage connection
  182. storage_driver = cfg.CONF['service:central'].storage_driver
  183. self._storage = storage.get_storage(storage_driver)
  184. return self._storage
  185. @property
  186. def service_name(self):
  187. return cfg.CONF['service:central'].central_topic
  188. def start(self):
  189. if (cfg.CONF['service:central'].managed_resource_tenant_id ==
  190. "00000000-0000-0000-0000-000000000000"):
  191. msg = _LW("Managed Resource Tenant ID is not properly configured")
  192. LOG.warning(msg)
  193. super(Service, self).start()
  194. def stop(self):
  195. super(Service, self).stop()
  196. @property
  197. def mdns_api(self):
  198. return mdns_rpcapi.MdnsAPI.get_instance()
  199. @property
  200. def pool_manager_api(self):
  201. return pool_manager_rpcapi.PoolManagerAPI.get_instance()
  202. @property
  203. def worker_api(self):
  204. return worker_rpcapi.WorkerAPI.get_instance()
  205. @property
  206. def zone_api(self):
  207. # TODO(timsim): Remove this when pool_manager_api is gone
  208. if cfg.CONF['service:worker'].enabled:
  209. return self.worker_api
  210. return self.pool_manager_api
  211. def _is_valid_zone_name(self, context, zone_name):
  212. # Validate zone name length
  213. if len(zone_name) > cfg.CONF['service:central'].max_zone_name_len:
  214. raise exceptions.InvalidZoneName('Name too long')
  215. # Break the zone name up into its component labels
  216. zone_labels = zone_name.strip('.').split('.')
  217. # We need more than 1 label.
  218. if len(zone_labels) <= 1:
  219. raise exceptions.InvalidZoneName('More than one label is '
  220. 'required')
  221. # Check the TLD for validity if there are entries in the database
  222. if self.storage.find_tlds({}):
  223. LOG.info(_LI("Checking for TLDs"))
  224. try:
  225. self.storage.find_tld(context, {'name': zone_labels[-1]})
  226. except exceptions.TldNotFound:
  227. raise exceptions.InvalidZoneName('Invalid TLD')
  228. # Now check that the zone name is not the same as a TLD
  229. try:
  230. stripped_zone_name = zone_name.rstrip('.').lower()
  231. self.storage.find_tld(
  232. context,
  233. {'name': stripped_zone_name})
  234. except exceptions.TldNotFound:
  235. pass
  236. else:
  237. raise exceptions.InvalidZoneName(
  238. 'Zone name cannot be the same as a TLD')
  239. # Check zone name blacklist
  240. if self._is_blacklisted_zone_name(context, zone_name):
  241. # Some users are allowed bypass the blacklist.. Is this one?
  242. if not policy.check('use_blacklisted_zone', context,
  243. do_raise=False):
  244. raise exceptions.InvalidZoneName('Blacklisted zone name')
  245. return True
  246. def _is_valid_recordset_name(self, context, zone, recordset_name):
  247. if not recordset_name.endswith('.'):
  248. raise ValueError('Please supply a FQDN')
  249. # Validate record name length
  250. max_len = cfg.CONF['service:central'].max_recordset_name_len
  251. if len(recordset_name) > max_len:
  252. raise exceptions.InvalidRecordSetName('Name too long')
  253. # RecordSets must be contained in the parent zone
  254. if (recordset_name != zone['name']
  255. and not recordset_name.endswith("." + zone['name'])):
  256. raise exceptions.InvalidRecordSetLocation(
  257. 'RecordSet is not contained within it\'s parent zone')
  258. def _is_valid_recordset_placement(self, context, zone, recordset_name,
  259. recordset_type, recordset_id=None):
  260. # CNAME's must not be created at the zone apex.
  261. if recordset_type == 'CNAME' and recordset_name == zone.name:
  262. raise exceptions.InvalidRecordSetLocation(
  263. 'CNAME recordsets may not be created at the zone apex')
  264. # CNAME's must not share a name with other recordsets
  265. criterion = {
  266. 'zone_id': zone.id,
  267. 'name': recordset_name,
  268. }
  269. if recordset_type != 'CNAME':
  270. criterion['type'] = 'CNAME'
  271. recordsets = self.storage.find_recordsets(context, criterion)
  272. if ((len(recordsets) == 1 and recordsets[0].id != recordset_id)
  273. or len(recordsets) > 1):
  274. raise exceptions.InvalidRecordSetLocation(
  275. 'CNAME recordsets may not share a name with any other records')
  276. return True
  277. def _is_valid_recordset_placement_subzone(self, context, zone,
  278. recordset_name,
  279. criterion=None):
  280. """
  281. Check that the placement of the requested rrset belongs to any of the
  282. zones subzones..
  283. """
  284. LOG.debug("Checking if %s belongs in any of %s subzones" %
  285. (recordset_name, zone.name))
  286. criterion = criterion or {}
  287. context = context.elevated(all_tenants=True)
  288. if zone.name == recordset_name:
  289. return
  290. child_zones = self.storage.find_zones(
  291. context, {"parent_zone_id": zone.id})
  292. for child_zone in child_zones:
  293. try:
  294. self._is_valid_recordset_name(
  295. context, child_zone, recordset_name)
  296. except Exception:
  297. continue
  298. else:
  299. msg = 'RecordSet belongs in a child zone: %s' % \
  300. child_zone['name']
  301. raise exceptions.InvalidRecordSetLocation(msg)
  302. def _is_valid_recordset_records(self, recordset):
  303. """
  304. Check to make sure that the records in the recordset
  305. follow the rules, and won't blow up on the nameserver.
  306. """
  307. try:
  308. recordset.records
  309. except (AttributeError, exceptions.RelationNotLoaded):
  310. pass
  311. else:
  312. if len(recordset.records) > 1 and recordset.type == 'CNAME':
  313. raise exceptions.BadRequest(
  314. 'CNAME recordsets may not have more than 1 record'
  315. )
  316. def _is_blacklisted_zone_name(self, context, zone_name):
  317. """
  318. Ensures the provided zone_name is not blacklisted.
  319. """
  320. blacklists = self.storage.find_blacklists(context)
  321. class Timeout(Exception):
  322. pass
  323. def _handle_timeout(signum, frame):
  324. raise Timeout()
  325. signal.signal(signal.SIGALRM, _handle_timeout)
  326. try:
  327. for blacklist in blacklists:
  328. signal.setitimer(signal.ITIMER_REAL, 0.02)
  329. try:
  330. if bool(re.search(blacklist.pattern, zone_name)):
  331. return True
  332. finally:
  333. signal.setitimer(signal.ITIMER_REAL, 0)
  334. except Timeout:
  335. LOG.critical(_LC(
  336. 'Blacklist regex (%(pattern)s) took too long to evaluate '
  337. 'against zone name (%(zone_name)s') %
  338. {
  339. 'pattern': blacklist.pattern,
  340. 'zone_name': zone_name
  341. }
  342. )
  343. return True
  344. return False
  345. def _is_subzone(self, context, zone_name, pool_id):
  346. """
  347. Ensures the provided zone_name is the subzone
  348. of an existing zone (checks across all tenants)
  349. """
  350. context = context.elevated(all_tenants=True)
  351. # Break the name up into it's component labels
  352. labels = zone_name.split(".")
  353. criterion = {"pool_id": pool_id}
  354. i = 1
  355. # Starting with label #2, search for matching zone's in the database
  356. while (i < len(labels)):
  357. name = '.'.join(labels[i:])
  358. criterion["name"] = name
  359. try:
  360. zone = self.storage.find_zone(context, criterion)
  361. except exceptions.ZoneNotFound:
  362. i += 1
  363. else:
  364. return zone
  365. return False
  366. def _is_superzone(self, context, zone_name, pool_id):
  367. """
  368. Ensures the provided zone_name is the parent zone
  369. of an existing subzone (checks across all tenants)
  370. """
  371. context = context.elevated(all_tenants=True)
  372. # Create wildcard term to catch all subzones
  373. search_term = "%%.%(name)s" % {"name": zone_name}
  374. criterion = {'name': search_term, "pool_id": pool_id}
  375. subzones = self.storage.find_zones(context, criterion)
  376. return subzones
  377. def _is_valid_ttl(self, context, ttl):
  378. if ttl is None:
  379. return
  380. min_ttl = cfg.CONF['service:central'].min_ttl
  381. if min_ttl is not None and ttl < int(min_ttl):
  382. try:
  383. policy.check('use_low_ttl', context)
  384. except exceptions.Forbidden:
  385. raise exceptions.InvalidTTL('TTL is below the minimum: %s'
  386. % min_ttl)
  387. def _increment_zone_serial(self, context, zone, set_delayed_notify=False):
  388. """Update the zone serial and the SOA record
  389. Optionally set delayed_notify to have PM issue delayed notify
  390. """
  391. # Increment the serial number
  392. zone.serial = utils.increment_serial(zone.serial)
  393. if set_delayed_notify:
  394. zone.delayed_notify = True
  395. zone = self.storage.update_zone(context, zone)
  396. # Update SOA record
  397. self._update_soa(context, zone)
  398. return zone
  399. # SOA Recordset Methods
  400. def _build_soa_record(self, zone, ns_records):
  401. return "%s %s. %d %d %d %d %d" % (ns_records[0]['hostname'],
  402. zone['email'].replace("@", "."),
  403. zone['serial'],
  404. zone['refresh'],
  405. zone['retry'],
  406. zone['expire'],
  407. zone['minimum'])
  408. def _create_soa(self, context, zone):
  409. pool_ns_records = self._get_pool_ns_records(context, zone.pool_id)
  410. soa_values = [self._build_soa_record(zone, pool_ns_records)]
  411. recordlist = objects.RecordList(objects=[
  412. objects.Record(data=r, managed=True) for r in soa_values])
  413. values = {
  414. 'name': zone['name'],
  415. 'type': "SOA",
  416. 'records': recordlist
  417. }
  418. soa, zone = self._create_recordset_in_storage(
  419. context, zone, objects.RecordSet(**values),
  420. increment_serial=False)
  421. return soa
  422. def _update_soa(self, context, zone):
  423. # NOTE: We should not be updating SOA records when a zone is SECONDARY.
  424. if zone.type != 'PRIMARY':
  425. return
  426. # Get the pool for it's list of ns_records
  427. pool_ns_records = self._get_pool_ns_records(context, zone.pool_id)
  428. soa = self.find_recordset(context,
  429. criterion={'zone_id': zone['id'],
  430. 'type': "SOA"})
  431. soa.records[0].data = self._build_soa_record(zone, pool_ns_records)
  432. self._update_recordset_in_storage(context, zone, soa,
  433. increment_serial=False)
  434. # NS Recordset Methods
  435. def _create_ns(self, context, zone, ns_records):
  436. # NOTE: We should not be creating NS records when a zone is SECONDARY.
  437. if zone.type != 'PRIMARY':
  438. return
  439. # Create an NS record for each server
  440. recordlist = objects.RecordList(objects=[
  441. objects.Record(data=r, managed=True) for r in ns_records])
  442. values = {
  443. 'name': zone['name'],
  444. 'type': "NS",
  445. 'records': recordlist
  446. }
  447. ns, zone = self._create_recordset_in_storage(
  448. context, zone, objects.RecordSet(**values),
  449. increment_serial=False)
  450. return ns
  451. def _add_ns(self, context, zone, ns_record):
  452. # Get NS recordset
  453. # If the zone doesn't have an NS recordset yet, create one
  454. recordsets = self.find_recordsets(
  455. context, criterion={'zone_id': zone['id'], 'type': "NS"}
  456. )
  457. managed = []
  458. for rs in recordsets:
  459. if rs.managed:
  460. managed.append(rs)
  461. if len(managed) == 0:
  462. self._create_ns(context, zone, [ns_record])
  463. return
  464. elif len(managed) != 1:
  465. raise exceptions.RecordSetNotFound("No valid recordset found")
  466. ns_recordset = managed[0]
  467. # Add new record to recordset based on the new nameserver
  468. ns_recordset.records.append(
  469. objects.Record(data=ns_record, managed=True))
  470. self._update_recordset_in_storage(context, zone, ns_recordset,
  471. set_delayed_notify=True)
  472. def _delete_ns(self, context, zone, ns_record):
  473. ns_recordset = self.find_recordset(
  474. context, criterion={'zone_id': zone['id'], 'type': "NS"})
  475. for record in copy.deepcopy(ns_recordset.records):
  476. if record.data == ns_record:
  477. ns_recordset.records.remove(record)
  478. self._update_recordset_in_storage(context, zone, ns_recordset,
  479. set_delayed_notify=True)
  480. # Quota Enforcement Methods
  481. def _enforce_zone_quota(self, context, tenant_id):
  482. criterion = {'tenant_id': tenant_id}
  483. count = self.storage.count_zones(context, criterion)
  484. self.quota.limit_check(context, tenant_id, zones=count)
  485. def _enforce_recordset_quota(self, context, zone):
  486. # Ensure the recordsets per zone quota is OK
  487. criterion = {'zone_id': zone.id}
  488. count = self.storage.count_recordsets(context, criterion)
  489. self.quota.limit_check(
  490. context, zone.tenant_id, zone_recordsets=count)
  491. def _enforce_record_quota(self, context, zone, recordset):
  492. # Quotas don't apply to managed records.
  493. if recordset.managed:
  494. return
  495. # Ensure the records per zone quota is OK
  496. zone_criterion = {
  497. 'zone_id': zone.id,
  498. 'managed': False, # only include non-managed records
  499. }
  500. zone_records = self.storage.count_records(context, zone_criterion)
  501. recordset_criterion = {
  502. 'recordset_id': recordset.id,
  503. 'managed': False, # only include non-managed records
  504. }
  505. recordset_records = self.storage.count_records(
  506. context, recordset_criterion)
  507. # We need to check the current number of zones + the
  508. # changes that add, so lets get +/- from our recordset
  509. # records based on the action
  510. adjusted_zone_records = (
  511. zone_records - recordset_records + len(recordset.records))
  512. self.quota.limit_check(context, zone.tenant_id,
  513. zone_records=adjusted_zone_records)
  514. # Ensure the records per recordset quota is OK
  515. self.quota.limit_check(context, zone.tenant_id,
  516. recordset_records=recordset_records)
  517. # Misc Methods
  518. def get_absolute_limits(self, context):
  519. # NOTE(Kiall): Currently, we only have quota based limits..
  520. return self.quota.get_quotas(context, context.tenant)
  521. # Quota Methods
  522. def get_quotas(self, context, tenant_id):
  523. target = {'tenant_id': tenant_id}
  524. policy.check('get_quotas', context, target)
  525. if tenant_id != context.tenant and not context.all_tenants:
  526. raise exceptions.Forbidden()
  527. return self.quota.get_quotas(context, tenant_id)
  528. def get_quota(self, context, tenant_id, resource):
  529. target = {'tenant_id': tenant_id, 'resource': resource}
  530. policy.check('get_quota', context, target)
  531. return self.quota.get_quota(context, tenant_id, resource)
  532. @transaction
  533. def set_quota(self, context, tenant_id, resource, hard_limit):
  534. target = {
  535. 'tenant_id': tenant_id,
  536. 'resource': resource,
  537. 'hard_limit': hard_limit,
  538. }
  539. policy.check('set_quota', context, target)
  540. if tenant_id != context.tenant and not context.all_tenants:
  541. raise exceptions.Forbidden()
  542. return self.quota.set_quota(context, tenant_id, resource, hard_limit)
  543. @transaction
  544. def reset_quotas(self, context, tenant_id):
  545. target = {'tenant_id': tenant_id}
  546. policy.check('reset_quotas', context, target)
  547. self.quota.reset_quotas(context, tenant_id)
  548. # TLD Methods
  549. @notification('dns.tld.create')
  550. @transaction
  551. def create_tld(self, context, tld):
  552. policy.check('create_tld', context)
  553. # The TLD is only created on central's storage and not on the backend.
  554. created_tld = self.storage.create_tld(context, tld)
  555. return created_tld
  556. def find_tlds(self, context, criterion=None, marker=None, limit=None,
  557. sort_key=None, sort_dir=None):
  558. policy.check('find_tlds', context)
  559. return self.storage.find_tlds(context, criterion, marker, limit,
  560. sort_key, sort_dir)
  561. def get_tld(self, context, tld_id):
  562. policy.check('get_tld', context, {'tld_id': tld_id})
  563. return self.storage.get_tld(context, tld_id)
  564. @notification('dns.tld.update')
  565. @transaction
  566. def update_tld(self, context, tld):
  567. target = {
  568. 'tld_id': tld.obj_get_original_value('id'),
  569. }
  570. policy.check('update_tld', context, target)
  571. tld = self.storage.update_tld(context, tld)
  572. return tld
  573. @notification('dns.tld.delete')
  574. @transaction
  575. def delete_tld(self, context, tld_id):
  576. policy.check('delete_tld', context, {'tld_id': tld_id})
  577. tld = self.storage.delete_tld(context, tld_id)
  578. return tld
  579. # TSIG Key Methods
  580. @notification('dns.tsigkey.create')
  581. @transaction
  582. def create_tsigkey(self, context, tsigkey):
  583. policy.check('create_tsigkey', context)
  584. created_tsigkey = self.storage.create_tsigkey(context, tsigkey)
  585. # TODO(Ron): this method needs to do more than update storage.
  586. return created_tsigkey
  587. def find_tsigkeys(self, context, criterion=None, marker=None, limit=None,
  588. sort_key=None, sort_dir=None):
  589. policy.check('find_tsigkeys', context)
  590. return self.storage.find_tsigkeys(context, criterion, marker,
  591. limit, sort_key, sort_dir)
  592. def get_tsigkey(self, context, tsigkey_id):
  593. policy.check('get_tsigkey', context, {'tsigkey_id': tsigkey_id})
  594. return self.storage.get_tsigkey(context, tsigkey_id)
  595. @notification('dns.tsigkey.update')
  596. @transaction
  597. def update_tsigkey(self, context, tsigkey):
  598. target = {
  599. 'tsigkey_id': tsigkey.obj_get_original_value('id'),
  600. }
  601. policy.check('update_tsigkey', context, target)
  602. tsigkey = self.storage.update_tsigkey(context, tsigkey)
  603. # TODO(Ron): this method needs to do more than update storage.
  604. return tsigkey
  605. @notification('dns.tsigkey.delete')
  606. @transaction
  607. def delete_tsigkey(self, context, tsigkey_id):
  608. policy.check('delete_tsigkey', context, {'tsigkey_id': tsigkey_id})
  609. tsigkey = self.storage.delete_tsigkey(context, tsigkey_id)
  610. # TODO(Ron): this method needs to do more than update storage.
  611. return tsigkey
  612. # Tenant Methods
  613. def find_tenants(self, context):
  614. policy.check('find_tenants', context)
  615. return self.storage.find_tenants(context)
  616. def get_tenant(self, context, tenant_id):
  617. target = {
  618. 'tenant_id': tenant_id
  619. }
  620. policy.check('get_tenant', context, target)
  621. return self.storage.get_tenant(context, tenant_id)
  622. def count_tenants(self, context):
  623. policy.check('count_tenants', context)
  624. return self.storage.count_tenants(context)
  625. # Zone Methods
  626. def _generate_soa_refresh_interval(self):
  627. """Generate a random refresh interval to stagger AXFRs across multiple
  628. zones and resolvers
  629. maximum val: default_soa_refresh_min
  630. minimum val: default_soa_refresh_max
  631. """
  632. assert cfg.CONF.default_soa_refresh_min is not None
  633. assert cfg.CONF.default_soa_refresh_max is not None
  634. dispersion = (cfg.CONF.default_soa_refresh_max -
  635. cfg.CONF.default_soa_refresh_min) * random.random()
  636. refresh_time = cfg.CONF.default_soa_refresh_min + dispersion
  637. return int(refresh_time)
  638. def _get_pool_ns_records(self, context, pool_id):
  639. """Get pool ns_records using an elevated context and all_tenants = True
  640. :param pool_id: Pool ID
  641. :returns: ns_records
  642. """
  643. elevated_context = context.elevated(all_tenants=True)
  644. pool = self.storage.get_pool(elevated_context, pool_id)
  645. return pool.ns_records
  646. @notification('dns.domain.create')
  647. @notification('dns.zone.create')
  648. @synchronized_zone(new_zone=True)
  649. def create_zone(self, context, zone):
  650. """Create zone: perform checks and then call _create_zone()
  651. """
  652. # Default to creating in the current users tenant
  653. zone.tenant_id = zone.tenant_id or context.tenant
  654. target = {
  655. 'tenant_id': zone.tenant_id,
  656. 'zone_name': zone.name
  657. }
  658. policy.check('create_zone', context, target)
  659. # Ensure the tenant has enough quota to continue
  660. self._enforce_zone_quota(context, zone.tenant_id)
  661. # Ensure the zone name is valid
  662. self._is_valid_zone_name(context, zone.name)
  663. # Ensure TTL is above the minimum
  664. self._is_valid_ttl(context, zone.ttl)
  665. # Get a pool id
  666. zone.pool_id = self.scheduler.schedule_zone(context, zone)
  667. # Handle sub-zones appropriately
  668. parent_zone = self._is_subzone(
  669. context, zone.name, zone.pool_id)
  670. if parent_zone:
  671. if parent_zone.tenant_id == zone.tenant_id:
  672. # Record the Parent Zone ID
  673. zone.parent_zone_id = parent_zone.id
  674. else:
  675. raise exceptions.IllegalChildZone('Unable to create'
  676. 'subzone in another '
  677. 'tenants zone')
  678. # Handle super-zones appropriately
  679. subzones = self._is_superzone(context, zone.name, zone.pool_id)
  680. msg = 'Unable to create zone because another tenant owns a ' \
  681. 'subzone of the zone'
  682. if subzones:
  683. LOG.debug("Zone '{0}' is a superzone.".format(zone.name))
  684. for subzone in subzones:
  685. if subzone.tenant_id != zone.tenant_id:
  686. raise exceptions.IllegalParentZone(msg)
  687. # If this succeeds, subzone parent IDs will be updated
  688. # after zone is created
  689. # NOTE(kiall): Fetch the servers before creating the zone, this way
  690. # we can prevent zone creation if no servers are
  691. # configured.
  692. pool_ns_records = self._get_pool_ns_records(context, zone.pool_id)
  693. if len(pool_ns_records) == 0:
  694. LOG.critical(_LC('No nameservers configured. '
  695. 'Please create at least one nameserver'))
  696. raise exceptions.NoServersConfigured()
  697. # End of pre-flight checks, create zone
  698. return self._create_zone(context, zone, subzones)
  699. def _create_zone(self, context, zone, subzones):
  700. """Create zone straight away
  701. """
  702. if zone.type == 'SECONDARY' and zone.serial is None:
  703. zone.serial = 1
  704. # randomize the zone refresh time
  705. zone.refresh = self._generate_soa_refresh_interval()
  706. zone = self._create_zone_in_storage(context, zone)
  707. self.zone_api.create_zone(context, zone)
  708. if zone.type == 'SECONDARY':
  709. self.mdns_api.perform_zone_xfr(context, zone)
  710. # If zone is a superzone, update subzones
  711. # with new parent IDs
  712. for subzone in subzones:
  713. LOG.debug("Updating subzone '{0}' parent ID "
  714. "using superzone ID '{1}'"
  715. .format(subzone.name, zone.id))
  716. subzone.parent_zone_id = zone.id
  717. self.update_zone(context, subzone)
  718. return zone
  719. @transaction
  720. def _create_zone_in_storage(self, context, zone):
  721. zone.action = 'CREATE'
  722. zone.status = 'PENDING'
  723. zone = self.storage.create_zone(context, zone)
  724. pool_ns_records = self.get_zone_ns_records(context, zone['id'])
  725. # Create the SOA and NS recordsets for the new zone. The SOA
  726. # record will always be the first 'created_at' record for a zone.
  727. self._create_soa(context, zone)
  728. self._create_ns(context, zone, [n.hostname for n in pool_ns_records])
  729. if zone.obj_attr_is_set('recordsets'):
  730. for rrset in zone.recordsets:
  731. # This allows eventlet to yield, as this looping operation
  732. # can be very long-lived.
  733. time.sleep(0)
  734. self._create_recordset_in_storage(
  735. context, zone, rrset, increment_serial=False)
  736. return zone
  737. def get_zone(self, context, zone_id):
  738. """Get a zone, even if flagged for deletion
  739. """
  740. zone = self.storage.get_zone(context, zone_id)
  741. target = {
  742. 'zone_id': zone_id,
  743. 'zone_name': zone.name,
  744. 'tenant_id': zone.tenant_id
  745. }
  746. policy.check('get_zone', context, target)
  747. return zone
  748. def get_zone_ns_records(self, context, zone_id=None, criterion=None):
  749. if zone_id is None:
  750. policy.check('get_zone_ns_records', context)
  751. pool_id = cfg.CONF['service:central'].default_pool_id
  752. else:
  753. zone = self.storage.get_zone(context, zone_id)
  754. target = {
  755. 'zone_id': zone_id,
  756. 'zone_name': zone.name,
  757. 'tenant_id': zone.tenant_id
  758. }
  759. pool_id = zone.pool_id
  760. policy.check('get_zone_ns_records', context, target)
  761. # Need elevated context to get the pool
  762. elevated_context = context.elevated(all_tenants=True)
  763. # Get the pool for it's list of ns_records
  764. pool = self.storage.get_pool(elevated_context, pool_id)
  765. return pool.ns_records
  766. def find_zones(self, context, criterion=None, marker=None, limit=None,
  767. sort_key=None, sort_dir=None):
  768. """List existing zones including the ones flagged for deletion.
  769. """
  770. target = {'tenant_id': context.tenant}
  771. policy.check('find_zones', context, target)
  772. return self.storage.find_zones(context, criterion, marker, limit,
  773. sort_key, sort_dir)
  774. def find_zone(self, context, criterion=None):
  775. target = {'tenant_id': context.tenant}
  776. policy.check('find_zone', context, target)
  777. return self.storage.find_zone(context, criterion)
  778. @notification('dns.domain.update')
  779. @notification('dns.zone.update')
  780. @synchronized_zone()
  781. def update_zone(self, context, zone, increment_serial=True):
  782. """Update zone. Perform checks and then call _update_zone()
  783. :returns: updated zone
  784. """
  785. target = {
  786. 'zone_id': zone.obj_get_original_value('id'),
  787. 'zone_name': zone.obj_get_original_value('name'),
  788. 'tenant_id': zone.obj_get_original_value('tenant_id'),
  789. }
  790. policy.check('update_zone', context, target)
  791. changes = zone.obj_get_changes()
  792. # Ensure immutable fields are not changed
  793. if 'tenant_id' in changes:
  794. # TODO(kiall): Moving between tenants should be allowed, but the
  795. # current code will not take into account that
  796. # RecordSets and Records must also be moved.
  797. raise exceptions.BadRequest('Moving a zone between tenants is '
  798. 'not allowed')
  799. if 'name' in changes:
  800. raise exceptions.BadRequest('Renaming a zone is not allowed')
  801. # Ensure TTL is above the minimum
  802. ttl = changes.get('ttl')
  803. self._is_valid_ttl(context, ttl)
  804. return self._update_zone(context, zone, increment_serial, changes)
  805. def _update_zone(self, context, zone, increment_serial, changes):
  806. """Update zone
  807. """
  808. zone = self._update_zone_in_storage(
  809. context, zone, increment_serial=increment_serial)
  810. # Fire off a XFR
  811. if 'masters' in changes:
  812. self.mdns_api.perform_zone_xfr(context, zone)
  813. self.zone_api.update_zone(context, zone)
  814. return zone
  815. @transaction
  816. def _update_zone_in_storage(self, context, zone,
  817. increment_serial=True, set_delayed_notify=False):
  818. zone.action = 'UPDATE'
  819. zone.status = 'PENDING'
  820. if increment_serial:
  821. # _increment_zone_serial increments and updates the zone
  822. zone = self._increment_zone_serial(
  823. context, zone, set_delayed_notify=set_delayed_notify)
  824. else:
  825. zone = self.storage.update_zone(context, zone)
  826. return zone
  827. @notification('dns.domain.delete')
  828. @notification('dns.zone.delete')
  829. @synchronized_zone()
  830. def delete_zone(self, context, zone_id):
  831. """Delete or abandon a zone
  832. On abandon, delete the zone from the DB immediately.
  833. Otherwise, set action to DELETE and status to PENDING and poke
  834. Pool Manager's "delete_zone" to update the resolvers. PM will then
  835. poke back to set action to NONE and status to DELETED
  836. """
  837. zone = self.storage.get_zone(context, zone_id)
  838. target = {
  839. 'zone_id': zone_id,
  840. 'zone_name': zone.name,
  841. 'tenant_id': zone.tenant_id
  842. }
  843. if hasattr(context, 'abandon') and context.abandon:
  844. policy.check('abandon_zone', context, target)
  845. else:
  846. policy.check('delete_zone', context, target)
  847. # Prevent deletion of a zone which has child zones
  848. criterion = {'parent_zone_id': zone_id}
  849. if self.storage.count_zones(context, criterion) > 0:
  850. raise exceptions.ZoneHasSubZone('Please delete any subzones '
  851. 'before deleting this zone')
  852. if hasattr(context, 'abandon') and context.abandon:
  853. LOG.info(_LI("Abandoning zone '%(zone)s'"), {'zone': zone.name})
  854. zone = self.storage.delete_zone(context, zone.id)
  855. else:
  856. zone = self._delete_zone_in_storage(context, zone)
  857. self.zone_api.delete_zone(context, zone)
  858. return zone
  859. @transaction
  860. def _delete_zone_in_storage(self, context, zone):
  861. """Set zone action to DELETE and status to PENDING
  862. to have the zone soft-deleted later on
  863. """
  864. zone.action = 'DELETE'
  865. zone.status = 'PENDING'
  866. zone = self.storage.update_zone(context, zone)
  867. return zone
  868. def purge_zones(self, context, criterion, limit=None):
  869. """Purge deleted zones.
  870. :returns: number of purged zones
  871. """
  872. policy.check('purge_zones', context, criterion)
  873. LOG.debug("Performing purge with limit of %r and criterion of %r"
  874. % (limit, criterion))
  875. return self.storage.purge_zones(context, criterion, limit)
  876. def xfr_zone(self, context, zone_id):
  877. zone = self.storage.get_zone(context, zone_id)
  878. target = {
  879. 'zone_id': zone_id,
  880. 'zone_name': zone.name,
  881. 'tenant_id': zone.tenant_id
  882. }
  883. policy.check('xfr_zone', context, target)
  884. if zone.type != 'SECONDARY':
  885. msg = "Can't XFR a non Secondary zone."
  886. raise exceptions.BadRequest(msg)
  887. # Ensure the format of the servers are correct, then poll the
  888. # serial
  889. srv = random.choice(zone.masters)
  890. status, serial, retries = self.mdns_api.get_serial_number(
  891. context, zone, srv.host, srv.port, 3, 1, 3, 0)
  892. # Perform XFR if serial's are not equal
  893. if serial > zone.serial:
  894. msg = _LI(
  895. "Serial %(srv_serial)d is not equal to zone's %(serial)d,"
  896. " performing AXFR")
  897. LOG.info(
  898. msg, {"srv_serial": serial, "serial": zone.serial})
  899. self.mdns_api.perform_zone_xfr(context, zone)
  900. def count_zones(self, context, criterion=None):
  901. if criterion is None:
  902. criterion = {}
  903. target = {
  904. 'tenant_id': criterion.get('tenant_id', None)
  905. }
  906. policy.check('count_zones', context, target)
  907. return self.storage.count_zones(context, criterion)
  908. # Report combining all the count reports based on criterion
  909. def count_report(self, context, criterion=None):
  910. reports = []
  911. if criterion is None:
  912. # Get all the reports
  913. reports.append({'zones': self.count_zones(context),
  914. 'records': self.count_records(context),
  915. 'tenants': self.count_tenants(context)})
  916. elif criterion == 'zones':
  917. reports.append({'zones': self.count_zones(context)})
  918. elif criterion == 'zones_delayed_notify':
  919. num_zones = self.count_zones(context, criterion=dict(
  920. delayed_notify=True))
  921. reports.append({'zones_delayed_notify': num_zones})
  922. elif criterion == 'records':
  923. reports.append({'records': self.count_records(context)})
  924. elif criterion == 'tenants':
  925. reports.append({'tenants': self.count_tenants(context)})
  926. else:
  927. raise exceptions.ReportNotFound()
  928. return reports
  929. @notification('dns.zone.touch')
  930. @synchronized_zone()
  931. def touch_zone(self, context, zone_id):
  932. zone = self.storage.get_zone(context, zone_id)
  933. target = {
  934. 'zone_id': zone_id,
  935. 'zone_name': zone.name,
  936. 'tenant_id': zone.tenant_id
  937. }
  938. policy.check('touch_zone', context, target)
  939. self._touch_zone_in_storage(context, zone)
  940. self.zone_api.update_zone(context, zone)
  941. return zone
  942. @transaction
  943. def _touch_zone_in_storage(self, context, zone):
  944. zone = self._increment_zone_serial(context, zone)
  945. return zone
  946. # RecordSet Methods
  947. @notification('dns.recordset.create')
  948. @synchronized_zone()
  949. def create_recordset(self, context, zone_id, recordset,
  950. increment_serial=True):
  951. zone = self.storage.get_zone(context, zone_id)
  952. # Don't allow updates to zones that are being deleted
  953. if zone.action == 'DELETE':
  954. raise exceptions.BadRequest('Can not update a deleting zone')
  955. target = {
  956. 'zone_id': zone_id,
  957. 'zone_name': zone.name,
  958. 'zone_type': zone.type,
  959. 'recordset_name': recordset.name,
  960. 'tenant_id': zone.tenant_id,
  961. }
  962. policy.check('create_recordset', context, target)
  963. recordset, zone = self._create_recordset_in_storage(
  964. context, zone, recordset, increment_serial=increment_serial)
  965. self.zone_api.update_zone(context, zone)
  966. recordset.zone_name = zone.name
  967. recordset.obj_reset_changes(['zone_name'])
  968. return recordset
  969. def _validate_recordset(self, context, zone, recordset):
  970. # See if we're validating an existing or new recordset
  971. recordset_id = None
  972. if hasattr(recordset, 'id'):
  973. recordset_id = recordset.id
  974. # Ensure TTL is above the minimum
  975. if not recordset_id:
  976. ttl = getattr(recordset, 'ttl', None)
  977. else:
  978. changes = recordset.obj_get_changes()
  979. ttl = changes.get('ttl', None)
  980. self._is_valid_ttl(context, ttl)
  981. # Ensure the recordset name and placement is valid
  982. self._is_valid_recordset_name(context, zone, recordset.name)
  983. self._is_valid_recordset_placement(
  984. context, zone, recordset.name, recordset.type, recordset_id)
  985. self._is_valid_recordset_placement_subzone(
  986. context, zone, recordset.name)
  987. # Validate the records
  988. self._is_valid_recordset_records(recordset)
  989. @transaction
  990. def _create_recordset_in_storage(self, context, zone, recordset,
  991. increment_serial=True):
  992. # Ensure the tenant has enough quota to continue
  993. self._enforce_recordset_quota(context, zone)
  994. self._validate_recordset(context, zone, recordset)
  995. if recordset.obj_attr_is_set('records') and len(recordset.records) > 0:
  996. # Ensure the tenant has enough zone record quotas to
  997. # create new records
  998. self._enforce_record_quota(context, zone, recordset)
  999. if increment_serial:
  1000. # update the zone's status and increment the serial
  1001. zone = self._update_zone_in_storage(
  1002. context, zone, increment_serial)
  1003. for record in recordset.records:
  1004. record.action = 'CREATE'
  1005. record.status = 'PENDING'
  1006. record.serial = zone.serial
  1007. recordset = self.storage.create_recordset(context, zone.id,
  1008. recordset)
  1009. # Return the zone too in case it was updated
  1010. return (recordset, zone)
  1011. def get_recordset(self, context, zone_id, recordset_id):
  1012. recordset = self.storage.get_recordset(context, recordset_id)
  1013. if zone_id:
  1014. zone = self.storage.get_zone(context, zone_id)
  1015. # Ensure the zone_id matches the record's zone_id
  1016. if zone.id != recordset.zone_id:
  1017. raise exceptions.RecordSetNotFound()
  1018. else:
  1019. zone = self.storage.get_zone(context, recordset.zone_id)
  1020. target = {
  1021. 'zone_id': zone.id,
  1022. 'zone_name': zone.name,
  1023. 'recordset_id': recordset.id,
  1024. 'tenant_id': zone.tenant_id,
  1025. }
  1026. policy.check('get_recordset', context, target)
  1027. recordset.zone_name = zone.name
  1028. recordset.obj_reset_changes(['zone_name'])
  1029. recordset = recordset
  1030. return recordset
  1031. def find_recordsets(self, context, criterion=None, marker=None, limit=None,
  1032. sort_key=None, sort_dir=None, force_index=False):
  1033. target = {'tenant_id': context.tenant}
  1034. policy.check('find_recordsets', context, target)
  1035. recordsets = self.storage.find_recordsets(context, criterion, marker,
  1036. limit, sort_key, sort_dir,
  1037. force_index)
  1038. return recordsets
  1039. def find_recordset(self, context, criterion=None):
  1040. target = {'tenant_id': context.tenant}
  1041. policy.check('find_recordset', context, target)
  1042. recordset = self.storage.find_recordset(context, criterion)
  1043. return recordset
  1044. def export_zone(self, context, zone_id):
  1045. zone = self.get_zone(context, zone_id)
  1046. criterion = {'zone_id': zone_id}
  1047. recordsets = self.storage.find_recordsets_export(context, criterion)
  1048. return utils.render_template('export-zone.jinja2',
  1049. zone=zone,
  1050. recordsets=recordsets)
  1051. @notification('dns.recordset.update')
  1052. @synchronized_zone()
  1053. def update_recordset(self, context, recordset, increment_serial=True):
  1054. zone_id = recordset.obj_get_original_value('zone_id')
  1055. zone = self.storage.get_zone(context, zone_id)
  1056. changes = recordset.obj_get_changes()
  1057. # Ensure immutable fields are not changed
  1058. if 'tenant_id' in changes:
  1059. raise exceptions.BadRequest('Moving a recordset between tenants '
  1060. 'is not allowed')
  1061. if 'zone_id' in changes or 'zone_name' in changes:
  1062. raise exceptions.BadRequest('Moving a recordset between zones '
  1063. 'is not allowed')
  1064. if 'type' in changes:
  1065. raise exceptions.BadRequest('Changing a recordsets type is not '
  1066. 'allowed')
  1067. # Don't allow updates to zones that are being deleted
  1068. if zone.action == 'DELETE':
  1069. raise exceptions.BadRequest('Can not update a deleting zone')
  1070. target = {
  1071. 'zone_id': recordset.obj_get_original_value('zone_id'),
  1072. 'zone_type': zone.type,
  1073. 'recordset_id': recordset.obj_get_original_value('id'),
  1074. 'zone_name': zone.name,
  1075. 'tenant_id': zone.tenant_id
  1076. }
  1077. policy.check('update_recordset', context, target)
  1078. if recordset.managed and not context.edit_managed_records:
  1079. raise exceptions.BadRequest('Managed records may not be updated')
  1080. recordset, zone = self._update_recordset_in_storage(
  1081. context, zone, recordset, increment_serial=increment_serial)
  1082. self.zone_api.update_zone(context, zone)
  1083. return recordset
  1084. @transaction
  1085. def _update_recordset_in_storage(self, context, zone, recordset,
  1086. increment_serial=True, set_delayed_notify=False):
  1087. self._validate_recordset(context, zone, recordset)
  1088. if increment_serial:
  1089. # update the zone's status and increment the serial
  1090. zone = self._update_zone_in_storage(
  1091. context, zone, increment_serial,
  1092. set_delayed_notify=set_delayed_notify)
  1093. if recordset.records:
  1094. for record in recordset.records:
  1095. if record.action != 'DELETE':
  1096. record.action = 'UPDATE'
  1097. record.status = 'PENDING'
  1098. record.serial = zone.serial
  1099. # Ensure the tenant has enough zone record quotas to
  1100. # create new records
  1101. self._enforce_record_quota(context, zone, recordset)
  1102. # Update the recordset
  1103. recordset = self.storage.update_recordset(context, recordset)
  1104. return (recordset, zone)
  1105. @notification('dns.recordset.delete')
  1106. @synchronized_zone()
  1107. def delete_recordset(self, context, zone_id, recordset_id,
  1108. increment_serial=True):
  1109. zone = self.storage.get_zone(context, zone_id)
  1110. recordset = self.storage.get_recordset(context, recordset_id)
  1111. # Ensure the zone_id matches the recordset's zone_id
  1112. if zone.id != recordset.zone_id:
  1113. raise exceptions.RecordSetNotFound()
  1114. # Don't allow updates to zones that are being deleted
  1115. if zone.action == 'DELETE':
  1116. raise exceptions.BadRequest('Can not update a deleting zone')
  1117. target = {
  1118. 'zone_id': zone_id,
  1119. 'zone_name': zone.name,
  1120. 'zone_type': zone.type,
  1121. 'recordset_id': recordset.id,
  1122. 'tenant_id': zone.tenant_id
  1123. }
  1124. policy.check('delete_recordset', context, target)
  1125. if recordset.managed and not context.edit_managed_records:
  1126. raise exceptions.BadRequest('Managed records may not be deleted')
  1127. recordset, zone = self._delete_recordset_in_storage(
  1128. context, zone, recordset, increment_serial=increment_serial)
  1129. self.zone_api.update_zone(context, zone)
  1130. recordset.zone_name = zone.name
  1131. recordset.obj_reset_changes(['zone_name'])
  1132. return recordset
  1133. @transaction
  1134. def _delete_recordset_in_storage(self, context, zone, recordset,
  1135. increment_serial=True):
  1136. if increment_serial:
  1137. # update the zone's status and increment the serial
  1138. zone = self._update_zone_in_storage(
  1139. context, zone, increment_serial)
  1140. if recordset.records:
  1141. for record in recordset.records:
  1142. record.action = 'DELETE'
  1143. record.status = 'PENDING'
  1144. record.serial = zone.serial
  1145. # Update the recordset's action/status and then delete it
  1146. self.storage.update_recordset(context, recordset)
  1147. recordset = self.storage.delete_recordset(context, recordset.id)
  1148. return (recordset, zone)
  1149. def count_recordsets(self, context, criterion=None):
  1150. if criterion is None:
  1151. criterion = {}
  1152. target = {
  1153. 'tenant_id': criterion.get('tenant_id', None)
  1154. }
  1155. policy.check('count_recordsets', context, target)
  1156. return self.storage.count_recordsets(context, criterion)
  1157. # Record Methods
  1158. @notification('dns.record.create')
  1159. @synchronized_zone()
  1160. def create_record(self, context, zone_id, recordset_id, record,
  1161. increment_serial=True):
  1162. zone = self.storage.get_zone(context, zone_id)
  1163. # Don't allow updates to zones that are being deleted
  1164. if zone.action == 'DELETE':
  1165. raise exceptions.BadRequest('Can not update a deleting zone')
  1166. recordset = self.storage.get_recordset(context, recordset_id)
  1167. target = {
  1168. 'zone_id': zone_id,
  1169. 'zone_name': zone.name,
  1170. 'zone_type': zone.type,
  1171. 'recordset_id': recordset_id,
  1172. 'recordset_name': recordset.name,
  1173. 'tenant_id': zone.tenant_id
  1174. }
  1175. policy.check('create_record', context, target)
  1176. record, zone = self._create_record_in_storage(
  1177. context, zone, recordset, record,
  1178. increment_serial=increment_serial)
  1179. self.zone_api.update_zone(context, zone)
  1180. return record
  1181. @transaction
  1182. def _create_record_in_storage(self, context, zone, recordset, record,
  1183. increment_serial=True):
  1184. # Ensure the tenant has enough quota to continue
  1185. self._enforce_record_quota(context, zone, recordset)
  1186. if increment_serial:
  1187. # update the zone's status and increment the serial
  1188. zone = self._update_zone_in_storage(
  1189. context, zone, increment_serial)
  1190. record.action = 'CREATE'
  1191. record.status = 'PENDING'
  1192. record.serial = zone.serial
  1193. record = self.storage.create_record(context, zone.id, recordset.id,
  1194. record)
  1195. return (record, zone)
  1196. def get_record(self, context, zone_id, recordset_id, record_id):
  1197. zone = self.storage.get_zone(context, zone_id)
  1198. recordset = self.storage.get_recordset(context, recordset_id)
  1199. record = self.storage.get_record(context, record_id)
  1200. # Ensure the zone_id matches the record's zone_id
  1201. if zone.id != record.zone_id:
  1202. raise exceptions.RecordNotFound()
  1203. # Ensure the recordset_id matches the record's recordset_id
  1204. if recordset.id != record.recordset_id:
  1205. raise exceptions.RecordNotFound()
  1206. target = {
  1207. 'zone_id': zone_id,
  1208. 'zone_name': zone.name,
  1209. 'recordset_id': recordset_id,
  1210. 'recordset_name': recordset.name,
  1211. 'record_id': record.id,
  1212. 'tenant_id': zone.tenant_id
  1213. }
  1214. policy.check('get_record', context, target)
  1215. return record
  1216. def find_records(self, context, criterion=None, marker=None, limit=None,
  1217. sort_key=None, sort_dir=None):
  1218. target = {'tenant_id': context.tenant}
  1219. policy.check('find_records', context, target)
  1220. return self.storage.find_records(context, criterion, marker, limit,
  1221. sort_key, sort_dir)
  1222. def find_record(self, context, criterion=None):
  1223. target = {'tenant_id': context.tenant}
  1224. policy.check('find_record', context, target)
  1225. return self.storage.find_record(context, criterion)
  1226. @notification('dns.record.update')
  1227. @synchronized_zone()
  1228. def update_record(self, context, record, increment_serial=True):
  1229. zone_id = record.obj_get_original_value('zone_id')
  1230. zone = self.storage.get_zone(context, zone_id)
  1231. # Don't allow updates to zones that are being deleted
  1232. if zone.action == 'DELETE':
  1233. raise exceptions.BadRequest('Can not update a deleting zone')
  1234. recordset_id = record.obj_get_original_value('recordset_id')
  1235. recordset = self.storage.get_recordset(context, recordset_id)
  1236. changes = record.obj_get_changes()
  1237. # Ensure immutable fields are not changed
  1238. if 'tenant_id' in changes:
  1239. raise exceptions.BadRequest('Moving a recordset between tenants '
  1240. 'is not allowed')
  1241. if 'zone_id' in changes:
  1242. raise exceptions.BadRequest('Moving a recordset between zones '
  1243. 'is not allowed')
  1244. if 'recordset_id' in changes:
  1245. raise exceptions.BadRequest('Moving a recordset between '
  1246. 'recordsets is not allowed')
  1247. target = {
  1248. 'zone_id': record.obj_get_original_value('zone_id'),
  1249. 'zone_name': zone.name,
  1250. 'zone_type': zone.type,
  1251. 'recordset_id': record.obj_get_original_value('recordset_id'),
  1252. 'recordset_name': recordset.name,
  1253. 'record_id': record.obj_get_original_value('id'),
  1254. 'tenant_id': zone.tenant_id
  1255. }
  1256. policy.check('update_record', context, target)
  1257. if recordset.managed and not context.edit_managed_records:
  1258. raise exceptions.BadRequest('Managed records may not be updated')
  1259. record, zone = self._update_record_in_storage(
  1260. context, zone, record, increment_serial=increment_serial)
  1261. self.zone_api.update_zone(context, zone)
  1262. return record
  1263. @transaction
  1264. def _update_record_in_storage(self, context, zone, record,
  1265. increment_serial=True):
  1266. if increment_serial:
  1267. # update the zone's status and increment the serial
  1268. zone = self._update_zone_in_storage(
  1269. context, zone, increment_serial)
  1270. record.action = 'UPDATE'
  1271. record.status = 'PENDING'
  1272. record.serial = zone.serial
  1273. # Update the record
  1274. record = self.storage.update_record(context, record)
  1275. return (record, zone)
  1276. @notification('dns.record.delete')
  1277. @synchronized_zone()
  1278. def delete_record(self, context, zone_id, recordset_id, record_id,
  1279. increment_serial=True):
  1280. zone = self.storage.get_zone(context, zone_id)
  1281. # Don't allow updates to zones that are being deleted
  1282. if zone.action == 'DELETE':
  1283. raise exceptions.BadRequest('Can not update a deleting zone')
  1284. recordset = self.storage.get_recordset(context, recordset_id)
  1285. record = self.storage.get_record(context, record_id)
  1286. # Ensure the zone_id matches the record's zone_id
  1287. if zone.id != record.zone_id:
  1288. raise exceptions.RecordNotFound()
  1289. # Ensure the recordset_id matches the record's recordset_id
  1290. if recordset.id != record.recordset_id:
  1291. raise exceptions.RecordNotFound()
  1292. target = {
  1293. 'zone_id': zone_id,
  1294. 'zone_name': zone.name,
  1295. 'zone_type': zone.type,
  1296. 'recordset_id': recordset_id,
  1297. 'recordset_name': recordset.name,
  1298. 'record_id': record.id,
  1299. 'tenant_id': zone.tenant_id
  1300. }
  1301. policy.check('delete_record', context, target)
  1302. if recordset.managed and not context.edit_managed_records:
  1303. raise exceptions.BadRequest('Managed records may not be deleted')
  1304. record, zone = self._delete_record_in_storage(
  1305. context, zone, record, increment_serial=increment_serial)
  1306. self.zone_api.update_zone(context, zone)
  1307. return record
  1308. @transaction
  1309. def _delete_record_in_storage(self, context, zone, record,
  1310. increment_serial=True):
  1311. if increment_serial:
  1312. # update the zone's status and increment the serial
  1313. zone = self._update_zone_in_storage(
  1314. context, zone, increment_serial)
  1315. record.action = 'DELETE'
  1316. record.status = 'PENDING'
  1317. record.serial = zone.serial
  1318. record = self.storage.update_record(context, record)
  1319. return (record, zone)
  1320. def count_records(self, context, criterion=None):
  1321. if criterion is None:
  1322. criterion = {}
  1323. target = {
  1324. 'tenant_id': criterion.get('tenant_id', None)
  1325. }
  1326. policy.check('count_records', context, target)
  1327. return self.storage.count_records(context, criterion)
  1328. # Diagnostics Methods
  1329. def _sync_zone(self, context, zone):
  1330. return self.pool_manager_api.update_zone(context, zone)
  1331. @transaction
  1332. def sync_zones(self, context):
  1333. policy.check('diagnostics_sync_zones', context)
  1334. zones = self.storage.find_zones(context)
  1335. results = {}
  1336. for zone in zones:
  1337. results[zone.id] = self._sync_zone(context, zone)
  1338. return results
  1339. @transaction
  1340. def sync_zone(self, context, zone_id):
  1341. zone = self.storage.get_zone(context, zone_id)
  1342. target = {
  1343. 'zone_id': zone_id,
  1344. 'zone_name': zone.name,
  1345. 'tenant_id': zone.tenant_id
  1346. }
  1347. policy.check('diagnostics_sync_zone', context, target)
  1348. return self._sync_zone(context, zone)
  1349. @transaction
  1350. def sync_record(self, context, zone_id, recordset_id, record_id):
  1351. zone = self.storage.get_zone(context, zone_id)
  1352. recordset = self.storage.get_recordset(context, recordset_id)
  1353. target = {
  1354. 'zone_id': zone_id,
  1355. 'zone_name': zone.name,
  1356. 'recordset_id': recordset_id,
  1357. 'recordset_name': recordset.name,
  1358. 'record_id': record_id,
  1359. 'tenant_id': zone.tenant_id
  1360. }
  1361. policy.check('diagnostics_sync_record', context, target)
  1362. self.zone_api.update_zone(context, zone)
  1363. def ping(self, context):
  1364. policy.check('diagnostics_ping', context)
  1365. # TODO(Ron): Handle this method properly.
  1366. try:
  1367. backend_status = {'status': None}
  1368. except Exception as e:
  1369. backend_status = {'status': False, 'message': str(e)}
  1370. try:
  1371. storage_status = self.storage.ping(context)
  1372. except Exception as e:
  1373. storage_status = {'status': False, 'message': str(e)}
  1374. if backend_status and storage_status:
  1375. status = True
  1376. else:
  1377. status = False
  1378. return {
  1379. 'host': cfg.CONF.host,
  1380. 'status': status,
  1381. 'backend': backend_status,
  1382. 'storage': storage_status
  1383. }
  1384. def _determine_floatingips(self, context, fips, records=None,
  1385. tenant_id=None):
  1386. """
  1387. Given the context or tenant, records and fips it returns the valid
  1388. floatingips either with a associated record or not. Deletes invalid
  1389. records also.
  1390. Returns a list of tuples with FloatingIPs and it's Record.
  1391. """
  1392. tenant_id = tenant_id or context.tenant
  1393. elevated_context = context.elevated(all_tenants=True,
  1394. edit_managed_records=True)
  1395. criterion = {
  1396. 'managed': True,
  1397. 'managed_resource_type': 'ptr:floatingip',
  1398. }
  1399. records = self.find_records(elevated_context, criterion)
  1400. records = dict([(r['managed_extra'], r) for r in records])
  1401. invalid = []
  1402. data = {}
  1403. # First populate the list of FIPS
  1404. for fip_key, fip_values in fips.items():
  1405. # Check if the FIP has a record
  1406. record = records.get(fip_values['address'])
  1407. # NOTE: Now check if it's owned by the tenant that actually has the
  1408. # FIP in the external service and if not invalidate it (delete it)
  1409. # thus not returning it with in the tuple with the FIP, but None..
  1410. if record:
  1411. record_tenant = record['managed_tenant_id']
  1412. if record_tenant != tenant_id:
  1413. msg = "Invalid FloatingIP %s belongs to %s but record " \
  1414. "owner %s"
  1415. LOG.debug(msg, fip_key, tenant_id, record_tenant)
  1416. invalid.append(record)
  1417. record = None
  1418. data[fip_key] = (fip_values, record)
  1419. return data, invalid
  1420. def _invalidate_floatingips(self, context, records):
  1421. """
  1422. Utility method to delete a list of records.
  1423. """
  1424. elevated_context = context.elevated(all_tenants=True,
  1425. edit_managed_records=True)
  1426. if len(records) > 0:
  1427. for r in records:
  1428. msg = 'Deleting record %s for FIP %s'
  1429. LOG.debug(msg, r['id'], r['managed_resource_id'])
  1430. self.delete_record(elevated_context, r['zone_id'],
  1431. r['recordset_id'], r['id'])
  1432. def _format_floatingips(self, context, data, recordsets=None):
  1433. """
  1434. Given a list of FloatingIP and Record tuples we look through creating
  1435. a new dict of FloatingIPs
  1436. """
  1437. elevated_context = context.elevated(all_tenants=True)
  1438. fips = objects.FloatingIPList()
  1439. for key, value in data.items():
  1440. fip, record = value
  1441. fip_ptr = objects.FloatingIP().from_dict({
  1442. 'address': fip['address'],
  1443. 'id': fip['id'],
  1444. 'region': fip['region'],
  1445. 'ptrdname': None,
  1446. 'ttl': None,
  1447. 'description': None,
  1448. 'action': None,
  1449. 'status': 'ACTIVE'
  1450. })
  1451. # TTL population requires a present record in order to find the
  1452. # RS or Zone
  1453. if record:
  1454. fip_ptr['action'] = record.action
  1455. fip_ptr['status'] = record.status
  1456. # We can have a recordset dict passed in
  1457. if (recordsets is not None and
  1458. record['recordset_id'] in recordsets):
  1459. recordset = recordsets[record['recordset_id']]
  1460. else:
  1461. recordset = self.storage.get_recordset(
  1462. elevated_context, record['recordset_id'])
  1463. if recordset['ttl'] is not None:
  1464. fip_ptr['ttl'] = recordset['ttl']
  1465. else:
  1466. zone = self.get_zone(
  1467. elevated_context, record['zone_id'])
  1468. fip_ptr['ttl'] = zone['ttl']
  1469. fip_ptr['ptrdname'] = record['data']
  1470. fip_ptr['description'] = record['description']
  1471. else:
  1472. LOG.debug("No record information found for %s" %
  1473. value[0]['id'])
  1474. # Store the "fip_record" with the region and it's id as key
  1475. fips.append(fip_ptr)
  1476. return fips
  1477. def _list_floatingips(self, context, region=None):
  1478. data = self.network_api.list_floatingips(context, region=region)
  1479. return self._list_to_dict(data, keys=['region', 'id'])
  1480. def _list_to_dict(self, data, keys=['id']):
  1481. new = {}
  1482. for i in data:
  1483. key = tuple([i[key] for key in keys])
  1484. new[key] = i
  1485. return new
  1486. def _get_floatingip(self, context, region, floatingip_id, fips):
  1487. if (region, floatingip_id) not in fips:
  1488. msg = 'FloatingIP %s in %s is not associated for tenant "%s"' % \
  1489. (floatingip_id, region, context.tenant)
  1490. raise exceptions.NotFound(msg)
  1491. return fips[region, floatingip_id]
  1492. # PTR ops
  1493. def list_floatingips(self, context):
  1494. """
  1495. List Floating IPs PTR
  1496. A) We have service_catalog in the context and do a lookup using the
  1497. token pr Neutron in the SC
  1498. B) We lookup FIPs using the configured values for this deployment.
  1499. """
  1500. elevated_context = context.elevated(all_tenants=True,
  1501. edit_managed_records=True)
  1502. tenant_fips = self._list_floatingips(context)
  1503. valid, invalid = self._determine_floatingips(
  1504. elevated_context, tenant_fips)
  1505. self._invalidate_floatingips(context, invalid)
  1506. return self._format_floatingips(context, valid)
  1507. def get_floatingip(self, context, region, floatingip_id):
  1508. """
  1509. Get Floating IP PTR
  1510. """
  1511. elevated_context = context.elevated(all_tenants=True)
  1512. tenant_fips = self._list_floatingips(context, region=region)
  1513. fip = self._get_floatingip(context, region, floatingip_id, tenant_fips)
  1514. result = self._list_to_dict([fip], keys=['region', 'id'])
  1515. valid, invalid = self._determine_floatingips(
  1516. elevated_context, result)
  1517. self._invalidate_floatingips(context, invalid)
  1518. return self._format_floatingips(context, valid)[0]
  1519. def _set_floatingip_reverse(self, context, region, floatingip_id, values):
  1520. """
  1521. Set the FloatingIP's PTR record based on values.
  1522. """
  1523. elevated_context = context.elevated(all_tenants=True,
  1524. edit_managed_records=True)
  1525. tenant_fips = self._list_floatingips(context, region=region)
  1526. fip = self._get_floatingip(context, region, floatingip_id, tenant_fips)
  1527. zone_name = self.network_api.address_zone(fip['address'])
  1528. # NOTE: Find existing zone or create it..
  1529. try:
  1530. zone = self.storage.find_zone(
  1531. elevated_context, {'name': zone_name})
  1532. except exceptions.ZoneNotFound:
  1533. msg = _LI(
  1534. 'Creating zone for %(fip_id)s:%(region)s - '
  1535. '%(fip_addr)s zone %(zonename)s'), \
  1536. {'fip_id': floatingip_id, 'region': region,
  1537. 'fip_addr': fip['address'], 'zonename': zone_name}
  1538. LOG.info(msg)
  1539. email = cfg.CONF['service:central'].managed_resource_email
  1540. tenant_id = cfg.CONF['service:central'].managed_resource_tenant_id
  1541. zone_values = {
  1542. 'type': 'PRIMARY',
  1543. 'name': zone_name,
  1544. 'email': email,
  1545. 'tenant_id': tenant_id
  1546. }
  1547. zone = self.create_zone(
  1548. elevated_context, objects.Zone(**zone_values))
  1549. record_name = self.network_api.address_name(fip['address'])
  1550. recordset_values = {
  1551. 'name': record_name,
  1552. 'type': 'PTR',
  1553. 'ttl': values.get('ttl', None)
  1554. }
  1555. try:
  1556. recordset = self.find_recordset(
  1557. elevated_context, {'name': record_name, 'type': 'PTR'})
  1558. # Update the recordset values
  1559. recordset.name = recordset_values['name']
  1560. recordset.type = recordset_values['type']
  1561. recordset.ttl = recordset_values['ttl']
  1562. recordset.zone_id = zone['id']
  1563. recordset = self.update_recordset(
  1564. elevated_context,
  1565. recordset=recordset)
  1566. # Delete the current records for the recordset
  1567. LOG.debug("Removing old Record")
  1568. for record in recordset.records:
  1569. self.delete_record(
  1570. elevated_context,
  1571. zone_id=recordset['zone_id'],
  1572. recordset_id=recordset['id'],
  1573. record_id=record['id'])
  1574. except exceptions.RecordSetNotFound:
  1575. recordset = self.create_recordset(
  1576. elevated_context,
  1577. zone_id=zone['id'],
  1578. recordset=objects.RecordSet(**recordset_values))
  1579. record_values = {
  1580. 'data': values['ptrdname'],
  1581. 'description': values['description'],
  1582. 'managed': True,
  1583. 'managed_extra': fip['address'],
  1584. 'managed_resource_id': floatingip_id,
  1585. 'managed_resource_region': region,
  1586. 'managed_resource_type': 'ptr:floatingip',
  1587. 'managed_tenant_id': context.tenant
  1588. }
  1589. record = self.create_record(
  1590. elevated_context,
  1591. zone_id=zone['id'],
  1592. recordset_id=recordset['id'],
  1593. record=objects.Record(**record_values))
  1594. return self._format_floatingips(
  1595. context, {(region, floatingip_id): (fip, record)},
  1596. {recordset['id']: recordset})[0]
  1597. def _unset_floatingip_reverse(self, context, region, floatingip_id):
  1598. """
  1599. Unset the FloatingIP PTR record based on the
  1600. Service's FloatingIP ID > managed_resource_id
  1601. Tenant ID > managed_tenant_id
  1602. We find the record based on the criteria and delete it or raise.
  1603. """
  1604. elevated_context = context.elevated(all_tenants=True,
  1605. edit_managed_records=True)
  1606. criterion = {
  1607. 'managed_resource_id': floatingip_id,
  1608. 'managed_tenant_id': context.tenant
  1609. }
  1610. try:
  1611. record = self.storage.find_record(
  1612. elevated_context, criterion=criterion)
  1613. except exceptions.RecordNotFound:
  1614. msg = 'No such FloatingIP %s:%s' % (region, floatingip_id)
  1615. raise exceptions.NotFound(msg)
  1616. self.delete_record(
  1617. elevated_context,
  1618. zone_id=record['zone_id'],
  1619. recordset_id=record['recordset_id'],
  1620. record_id=record['id'])
  1621. @transaction
  1622. def update_floatingip(self, context, region, floatingip_id, values):
  1623. """
  1624. We strictly see if values['ptrdname'] is str or None and set / unset
  1625. the requested FloatingIP's PTR record based on that.
  1626. """
  1627. if 'ptrdname' in values.obj_what_changed() and\
  1628. values['ptrdname'] is None:
  1629. self._unset_floatingip_reverse(context, region, floatingip_id)
  1630. elif isinstance(values['ptrdname'], six.string_types):
  1631. return self._set_floatingip_reverse(
  1632. context, region, floatingip_id, values)
  1633. # Blacklisted zones
  1634. @notification('dns.blacklist.create')
  1635. @transaction
  1636. def create_blacklist(self, context, blacklist):
  1637. policy.check('create_blacklist', context)
  1638. created_blacklist = self.storage.create_blacklist(context, blacklist)
  1639. return created_blacklist
  1640. def get_blacklist(self, context, blacklist_id):
  1641. policy.check('get_blacklist', context)
  1642. blacklist = self.storage.get_blacklist(context, blacklist_id)
  1643. return blacklist
  1644. def find_blacklists(self, context, criterion=None, marker=None,
  1645. limit=None, sort_key=None, sort_dir=None):
  1646. policy.check('find_blacklists', context)
  1647. blacklists = self.storage.find_blacklists(context, criterion,
  1648. marker, limit,
  1649. sort_key, sort_dir)
  1650. return blacklists
  1651. def find_blacklist(self, context, criterion):
  1652. policy.check('find_blacklist', context)
  1653. blacklist = self.storage.find_blacklist(context, criterion)
  1654. return blacklist
  1655. @notification('dns.blacklist.update')
  1656. @transaction
  1657. def update_blacklist(self, context, blacklist):
  1658. target = {
  1659. 'blacklist_id': blacklist.id,
  1660. }
  1661. policy.check('update_blacklist', context, target)
  1662. blacklist = self.storage.update_blacklist(context, blacklist)
  1663. return blacklist
  1664. @notification('dns.blacklist.delete')
  1665. @transaction
  1666. def delete_blacklist(self, context, blacklist_id):
  1667. policy.check('delete_blacklist', context)
  1668. blacklist = self.storage.delete_blacklist(context, blacklist_id)
  1669. return blacklist
  1670. # Server Pools
  1671. @notification('dns.pool.create')
  1672. @transaction
  1673. def create_pool(self, context, pool):
  1674. # Verify that there is a tenant_id
  1675. if pool.tenant_id is None:
  1676. pool.tenant_id = context.tenant
  1677. policy.check('create_pool', context)
  1678. created_pool = self.storage.create_pool(context, pool)
  1679. return created_pool
  1680. def find_pools(self, context, criterion=None, marker=None, limit=None,
  1681. sort_key=None, sort_dir=None):
  1682. policy.check('find_pools', context)
  1683. return self.storage.find_pools(context, criterion, marker, limit,
  1684. sort_key, sort_dir)
  1685. def find_pool(self, context, criterion=None):
  1686. policy.check('find_pool', context)
  1687. return self.storage.find_pool(context, criterion)
  1688. def get_pool(self, context, pool_id):
  1689. policy.check('get_pool', context)
  1690. return self.storage.get_pool(context, pool_id)
  1691. @notification('dns.pool.update')
  1692. @transaction
  1693. def update_pool(self, context, pool):
  1694. policy.check('update_pool', context)
  1695. # If there is a nameserver, then additional steps need to be done
  1696. # Since these are treated as mutable objects, we're only going to
  1697. # be comparing the nameserver.value which is the FQDN
  1698. if pool.obj_attr_is_set('ns_records'):
  1699. elevated_context = context.elevated(all_tenants=True)
  1700. # TODO(kiall): ListObjects should be able to give you their
  1701. # original set of values.
  1702. original_pool_ns_records = self._get_pool_ns_records(context,
  1703. pool.id)
  1704. # Find the current NS hostnames
  1705. existing_ns = set([n.hostname for n in original_pool_ns_records])
  1706. # Find the desired NS hostnames
  1707. request_ns = set([n.hostname for n in pool.ns_records])
  1708. # Get the NS's to be created and deleted, ignoring the ones that
  1709. # are in both sets, as those haven't changed.
  1710. # TODO(kiall): Factor in priority
  1711. create_ns = request_ns.difference(existing_ns)
  1712. delete_ns = existing_ns.difference(request_ns)
  1713. updated_pool = self.storage.update_pool(context, pool)
  1714. # After the update, handle new ns_records
  1715. for ns in create_ns:
  1716. # Create new NS recordsets for every zone
  1717. zones = self.find_zones(
  1718. context=elevated_context,
  1719. criterion={'pool_id': pool.id, 'action': '!DELETE'})
  1720. for z in zones:
  1721. self._add_ns(elevated_context, z, ns)
  1722. # Then handle the ns_records to delete
  1723. for ns in delete_ns:
  1724. # Cannot delete the last nameserver, so verify that first.
  1725. if len(pool.ns_records) == 0:
  1726. raise exceptions.LastServerDeleteNotAllowed(
  1727. "Not allowed to delete last of servers"
  1728. )
  1729. # Delete the NS record for every zone
  1730. zones = self.find_zones(
  1731. context=elevated_context,
  1732. criterion={'pool_id': pool.id})
  1733. for z in zones:
  1734. self._delete_ns(elevated_context, z, ns)
  1735. return updated_pool
  1736. @notification('dns.pool.delete')
  1737. @transaction
  1738. def delete_pool(self, context, pool_id):
  1739. policy.check('delete_pool', context)
  1740. # Make sure that there are no existing zones in the pool
  1741. elevated_context = context.elevated(all_tenants=True)
  1742. zones = self.find_zones(
  1743. context=elevated_context,
  1744. criterion={'pool_id': pool_id, 'action': '!DELETE'})
  1745. # If there are existing zones, do not delete the pool
  1746. LOG.debug("Zones is None? %r " % zones)
  1747. if len(zones) == 0:
  1748. pool = self.storage.delete_pool(context, pool_id)
  1749. else:
  1750. raise exceptions.InvalidOperation('pool must not contain zones')
  1751. return pool
  1752. # Pool Manager Integration
  1753. @notification('dns.domain.update')
  1754. @notification('dns.zone.update')
  1755. @transaction
  1756. @synchronized_zone()
  1757. def update_status(self, context, zone_id, status, serial):
  1758. """
  1759. :param context: Security context information.
  1760. :param zone_id: The ID of the designate zone.
  1761. :param status: The status, 'SUCCESS' or 'ERROR'.
  1762. :param serial: The consensus serial number for the zone.
  1763. :return: updated zone
  1764. """
  1765. # TODO(kiall): If the status is SUCCESS and the zone is already ACTIVE,
  1766. # we likely don't need to do anything.
  1767. self._update_record_status(context, zone_id, status, serial)
  1768. zone = self._update_zone_status(context, zone_id, status, serial)
  1769. return zone
  1770. def _update_zone_status(self, context, zone_id, status, serial):
  1771. """Update zone status in storage
  1772. :return: updated zone
  1773. """
  1774. zone = self.storage.get_zone(context, zone_id)
  1775. zone, deleted = self._update_zone_or_record_status(
  1776. zone, status, serial)
  1777. if zone.status != 'DELETED':
  1778. LOG.debug('Setting zone %s, serial %s: action %s, status %s'
  1779. % (zone.id, zone.serial, zone.action, zone.status))
  1780. self.storage.update_zone(context, zone)
  1781. if deleted:
  1782. LOG.debug('update_status: deleting %s' % zone.name)
  1783. self.storage.delete_zone(context, zone.id)
  1784. return zone
  1785. def _update_record_status(self, context, zone_id, status, serial):
  1786. """Update status on every record in a zone based on `serial`
  1787. :returns: updated records
  1788. """
  1789. criterion = {
  1790. 'zone_id': zone_id
  1791. }
  1792. if status == 'SUCCESS':
  1793. criterion.update({
  1794. 'status': ['PENDING', 'ERROR'],
  1795. 'serial': '<=%d' % serial,
  1796. })
  1797. elif status == 'ERROR' and serial == 0:
  1798. criterion.update({
  1799. 'status': 'PENDING',
  1800. })
  1801. elif status == 'ERROR':
  1802. criterion.update({
  1803. 'status': 'PENDING',
  1804. 'serial': '<=%d' % serial,
  1805. })
  1806. records = self.storage.find_records(context, criterion=criterion)
  1807. for record in records:
  1808. record, deleted = self._update_zone_or_record_status(
  1809. record, status, serial)
  1810. if record.obj_what_changed():
  1811. LOG.debug('Setting record %s, serial %s: action %s, status %s'
  1812. % (record.id, record.serial,
  1813. record.action, record.status))
  1814. self.storage.update_record(context, record)
  1815. # TODO(Ron): Including this to retain the current logic.
  1816. # We should NOT be deleting records. The record status should
  1817. # be used to indicate the record has been deleted.
  1818. if deleted:
  1819. LOG.debug('Deleting record %s, serial %s: action %s, status %s'
  1820. % (record.id, record.serial,
  1821. record.action, record.status))
  1822. self.storage.delete_record(context, record.id)
  1823. recordset = self.storage.get_recordset(
  1824. context, record.recordset_id)
  1825. if len(recordset.records) == 0:
  1826. self.storage.delete_recordset(context, recordset.id)
  1827. return records
  1828. @staticmethod
  1829. def _update_zone_or_record_status(zone_or_record, status, serial):
  1830. deleted = False
  1831. if status == 'SUCCESS':
  1832. if zone_or_record.action in ['CREATE', 'UPDATE'] \
  1833. and zone_or_record.status in ['PENDING', 'ERROR'] \
  1834. and serial >= zone_or_record.serial:
  1835. zone_or_record.action = 'NONE'
  1836. zone_or_record.status = 'ACTIVE'
  1837. elif zone_or_record.action == 'DELETE' \
  1838. and zone_or_record.status in ['PENDING', 'ERROR'] \
  1839. and serial >= zone_or_record.serial:
  1840. zone_or_record.action = 'NONE'
  1841. zone_or_record.status = 'DELETED'
  1842. deleted = True
  1843. elif status == 'ERROR':
  1844. if zone_or_record.status == 'PENDING' \
  1845. and (serial >= zone_or_record.serial or serial == 0):
  1846. zone_or_record.status = 'ERROR'
  1847. elif status == 'NO_ZONE':
  1848. if zone_or_record.action in ['CREATE', 'UPDATE']:
  1849. zone_or_record.action = 'CREATE'
  1850. zone_or_record.status = 'ERROR'
  1851. elif zone_or_record.action == 'DELETE':
  1852. zone_or_record.action = 'NONE'
  1853. zone_or_record.status = 'DELETED'
  1854. deleted = True
  1855. return zone_or_record, deleted
  1856. # Zone Transfers
  1857. def _transfer_key_generator(self, size=8):
  1858. chars = string.ascii_uppercase + string.digits
  1859. return ''.join(random.choice(chars) for _ in range(size))
  1860. @notification('dns.zone_transfer_request.create')
  1861. @transaction
  1862. def create_zone_transfer_request(self, context, zone_transfer_request):
  1863. # get zone
  1864. zone = self.get_zone(context, zone_transfer_request.zone_id)
  1865. # Don't allow transfers for zones that are being deleted
  1866. if zone.action == 'DELETE':
  1867. raise exceptions.BadRequest('Can not transfer a deleting zone')
  1868. target = {
  1869. 'tenant_id': zone.tenant_id,
  1870. }
  1871. policy.check('create_zone_transfer_request', context, target)
  1872. zone_transfer_request.key = self._transfer_key_generator()
  1873. if zone_transfer_request.tenant_id is None:
  1874. zone_transfer_request.tenant_id = context.tenant
  1875. created_zone_transfer_request = \
  1876. self.storage.create_zone_transfer_request(
  1877. context, zone_transfer_request)
  1878. return created_zone_transfer_request
  1879. def get_zone_transfer_request(self, context, zone_transfer_request_id):
  1880. elevated_context = context.elevated(all_tenants=True)
  1881. # Get zone transfer request
  1882. zone_transfer_request = self.storage.get_zone_transfer_request(
  1883. elevated_context, zone_transfer_request_id)
  1884. LOG.info(_LI('Target Tenant ID found - using scoped policy'))
  1885. target = {
  1886. 'target_tenant_id': zone_transfer_request.target_tenant_id,
  1887. 'tenant_id': zone_transfer_request.tenant_id,
  1888. }
  1889. policy.check('get_zone_transfer_request', context, target)
  1890. return zone_transfer_request
  1891. def find_zone_transfer_requests(self, context, criterion=None, marker=None,
  1892. limit=None, sort_key=None, sort_dir=None):
  1893. policy.check('find_zone_transfer_requests', context)
  1894. requests = self.storage.find_zone_transfer_requests(
  1895. context, criterion,
  1896. marker, limit,
  1897. sort_key, sort_dir)
  1898. return requests
  1899. def find_zone_transfer_request(self, context, criterion):
  1900. target = {
  1901. 'tenant_id': context.tenant,
  1902. }
  1903. policy.check('find_zone_transfer_request', context, target)
  1904. return self.storage.find_zone_transfer_requests(context, criterion)
  1905. @notification('dns.zone_transfer_request.update')
  1906. @transaction
  1907. def update_zone_transfer_request(self, context, zone_transfer_request):
  1908. if 'zone_id' in zone_transfer_request.obj_what_changed():
  1909. raise exceptions.InvalidOperation('Zone cannot be changed')
  1910. target = {
  1911. 'tenant_id': zone_transfer_request.tenant_id,
  1912. }
  1913. policy.check('update_zone_transfer_request', context, target)
  1914. request = self.storage.update_zone_transfer_request(
  1915. context, zone_transfer_request)
  1916. return request
  1917. @notification('dns.zone_transfer_request.delete')
  1918. @transaction
  1919. def delete_zone_transfer_request(self, context, zone_transfer_request_id):
  1920. # Get zone transfer request
  1921. zone_transfer_request = self.storage.get_zone_transfer_request(
  1922. context, zone_transfer_request_id)
  1923. target = {
  1924. 'tenant_id': zone_transfer_request.tenant_id,
  1925. }
  1926. policy.check('delete_zone_transfer_request', context, target)
  1927. return self.storage.delete_zone_transfer_request(
  1928. context,
  1929. zone_transfer_request_id)
  1930. @notification('dns.zone_transfer_accept.create')
  1931. @transaction
  1932. def create_zone_transfer_accept(self, context, zone_transfer_accept):
  1933. elevated_context = context.elevated(all_tenants=True)
  1934. zone_transfer_request = self.get_zone_transfer_request(
  1935. context, zone_transfer_accept.zone_transfer_request_id)
  1936. zone_transfer_accept.zone_id = zone_transfer_request.zone_id
  1937. if zone_transfer_request.status != 'ACTIVE':
  1938. if zone_transfer_request.status == 'COMPLETE':
  1939. raise exceptions.InvaildZoneTransfer(
  1940. 'Zone Transfer Request has been used')
  1941. raise exceptions.InvaildZoneTransfer(
  1942. 'Zone Transfer Request Invalid')
  1943. if zone_transfer_request.key != zone_transfer_accept.key:
  1944. raise exceptions.IncorrectZoneTransferKey(
  1945. 'Key does not match stored key for request')
  1946. target = {
  1947. 'target_tenant_id': zone_transfer_request.target_tenant_id
  1948. }
  1949. policy.check('create_zone_transfer_accept', context, target)
  1950. if zone_transfer_accept.tenant_id is None:
  1951. zone_transfer_accept.tenant_id = context.tenant
  1952. created_zone_transfer_accept = \
  1953. self.storage.create_zone_transfer_accept(
  1954. context, zone_transfer_accept)
  1955. try:
  1956. zone = self.storage.get_zone(
  1957. elevated_context,
  1958. zone_transfer_request.zone_id)
  1959. # Don't allow transfers for zones that are being deleted
  1960. if zone.action == 'DELETE':
  1961. raise exceptions.BadRequest('Can not transfer a deleting zone')
  1962. zone.tenant_id = zone_transfer_accept.tenant_id
  1963. self.storage.update_zone(elevated_context, zone)
  1964. except Exception:
  1965. created_zone_transfer_accept.status = 'ERROR'
  1966. self.storage.update_zone_transfer_accept(
  1967. context, created_zone_transfer_accept)
  1968. raise
  1969. else:
  1970. created_zone_transfer_accept.status = 'COMPLETE'
  1971. zone_transfer_request.status = 'COMPLETE'
  1972. self.storage.update_zone_transfer_accept(
  1973. context, created_zone_transfer_accept)
  1974. self.storage.update_zone_transfer_request(
  1975. elevated_context, zone_transfer_request)
  1976. return created_zone_transfer_accept
  1977. def get_zone_transfer_accept(self, context, zone_transfer_accept_id):
  1978. # Get zone transfer accept
  1979. zone_transfer_accept = self.storage.get_zone_transfer_accept(
  1980. context, zone_transfer_accept_id)
  1981. target = {
  1982. 'tenant_id': zone_transfer_accept.tenant_id
  1983. }
  1984. policy.check('get_zone_transfer_accept', context, target)
  1985. return zone_transfer_accept
  1986. def find_zone_transfer_accepts(self, context, criterion=None, marker=None,
  1987. limit=None, sort_key=None, sort_dir=None):
  1988. policy.check('find_zone_transfer_accepts', context)
  1989. return self.storage.find_zone_transfer_accepts(context, criterion,
  1990. marker, limit,
  1991. sort_key, sort_dir)
  1992. def find_zone_transfer_accept(self, context, criterion):
  1993. policy.check('find_zone_transfer_accept', context)
  1994. return self.storage.find_zone_transfer_accept(context, criterion)
  1995. @notification('dns.zone_transfer_accept.update')
  1996. @transaction
  1997. def update_zone_transfer_accept(self, context, zone_transfer_accept):
  1998. target = {
  1999. 'tenant_id': zone_transfer_accept.tenant_id
  2000. }
  2001. policy.check('update_zone_transfer_accept', context, target)
  2002. accept = self.storage.update_zone_transfer_accept(
  2003. context, zone_transfer_accept)
  2004. return accept
  2005. @notification('dns.zone_transfer_accept.delete')
  2006. @transaction
  2007. def delete_zone_transfer_accept(self, context, zone_transfer_accept_id):
  2008. # Get zone transfer accept
  2009. zt_accept = self.storage.get_zone_transfer_accept(
  2010. context, zone_transfer_accept_id)
  2011. target = {
  2012. 'tenant_id': zt_accept.tenant_id
  2013. }
  2014. policy.check('delete_zone_transfer_accept', context, target)
  2015. return self.storage.delete_zone_transfer_accept(
  2016. context,
  2017. zone_transfer_accept_id)
  2018. # Zone Import Methods
  2019. @notification('dns.zone_import.create')
  2020. def create_zone_import(self, context, request_body):
  2021. target = {'tenant_id': context.tenant}
  2022. policy.check('create_zone_import', context, target)
  2023. values = {
  2024. 'status': 'PENDING',
  2025. 'message': None,
  2026. 'zone_id': None,
  2027. 'tenant_id': context.tenant,
  2028. 'task_type': 'IMPORT'
  2029. }
  2030. zone_import = objects.ZoneImport(**values)
  2031. created_zone_import = self.storage.create_zone_import(context,
  2032. zone_import)
  2033. self.tg.add_thread(self._import_zone, context, created_zone_import,
  2034. request_body)
  2035. return created_zone_import
  2036. def _import_zone(self, context, zone_import, request_body):
  2037. def _import(self, context, zone_import, request_body):
  2038. # Dnspython needs a str instead of a unicode object
  2039. if six.PY2:
  2040. request_body = str(request_body)
  2041. zone = None
  2042. try:
  2043. dnspython_zone = dnszone.from_text(
  2044. request_body,
  2045. # Don't relativize, or we end up with '@' record names.
  2046. relativize=False,
  2047. # Don't check origin, we allow missing NS records
  2048. # (missing SOA records are taken care of in _create_zone).
  2049. check_origin=False)
  2050. zone = dnsutils.from_dnspython_zone(dnspython_zone)
  2051. zone.type = 'PRIMARY'
  2052. for rrset in list(zone.recordsets):
  2053. if rrset.type in ('NS', 'SOA'):
  2054. zone.recordsets.remove(rrset)
  2055. except dnszone.UnknownOrigin:
  2056. zone_import.message = ('The $ORIGIN statement is required and'
  2057. ' must be the first statement in the'
  2058. ' zonefile.')
  2059. zone_import.status = 'ERROR'
  2060. except dnsexception.SyntaxError:
  2061. zone_import.message = 'Malformed zonefile.'
  2062. zone_import.status = 'ERROR'
  2063. except exceptions.BadRequest:
  2064. zone_import.message = 'An SOA record is required.'
  2065. zone_import.status = 'ERROR'
  2066. except Exception as e:
  2067. msg = _LE('An undefined error occurred during zone import')
  2068. LOG.exception(msg)
  2069. msg = 'An undefined error occurred. %s'\
  2070. % six.text_type(e)[:130]
  2071. zone_import.message = msg
  2072. zone_import.status = 'ERROR'
  2073. return zone, zone_import
  2074. # Execute the import in a real Python thread
  2075. zone, zone_import = tpool.execute(_import, self, context,
  2076. zone_import, request_body)
  2077. # If the zone import was valid, create the zone
  2078. if zone_import.status != 'ERROR':
  2079. try:
  2080. zone = self.create_zone(context, zone)
  2081. zone_import.status = 'COMPLETE'
  2082. zone_import.zone_id = zone.id
  2083. zone_import.message = '%(name)s imported' % {'name':
  2084. zone.name}
  2085. except exceptions.DuplicateZone:
  2086. zone_import.status = 'ERROR'
  2087. zone_import.message = 'Duplicate zone.'
  2088. except exceptions.InvalidTTL as e:
  2089. zone_import.status = 'ERROR'
  2090. zone_import.message = six.text_type(e)
  2091. except Exception as e:
  2092. msg = _LE('An undefined error occurred during zone '
  2093. 'import creation')
  2094. LOG.exception(msg)
  2095. msg = 'An undefined error occurred. %s'\
  2096. % six.text_type(e)[:130]
  2097. zone_import.message = msg
  2098. zone_import.status = 'ERROR'
  2099. self.update_zone_import(context, zone_import)
  2100. def find_zone_imports(self, context, criterion=None, marker=None,
  2101. limit=None, sort_key=None, sort_dir=None):
  2102. target = {'tenant_id': context.tenant}
  2103. policy.check('find_zone_imports', context, target)
  2104. criterion = {
  2105. 'task_type': 'IMPORT'
  2106. }
  2107. return self.storage.find_zone_imports(context, criterion, marker,
  2108. limit, sort_key, sort_dir)
  2109. def get_zone_import(self, context, zone_import_id):
  2110. target = {'tenant_id': context.tenant}
  2111. policy.check('get_zone_import', context, target)
  2112. return self.storage.get_zone_import(context, zone_import_id)
  2113. @notification('dns.zone_import.update')
  2114. def update_zone_import(self, context, zone_import):
  2115. target = {
  2116. 'tenant_id': zone_import.tenant_id,
  2117. }
  2118. policy.check('update_zone_import', context, target)
  2119. return self.storage.update_zone_import(context, zone_import)
  2120. @notification('dns.zone_import.delete')
  2121. @transaction
  2122. def delete_zone_import(self, context, zone_import_id):
  2123. target = {
  2124. 'zone_import_id': zone_import_id,
  2125. 'tenant_id': context.tenant
  2126. }
  2127. policy.check('delete_zone_import', context, target)
  2128. zone_import = self.storage.delete_zone_import(context, zone_import_id)
  2129. return zone_import
  2130. # Zone Export Methods
  2131. @notification('dns.zone_export.create')
  2132. def create_zone_export(self, context, zone_id):
  2133. # Try getting the zone to ensure it exists
  2134. zone = self.storage.get_zone(context, zone_id)
  2135. target = {'tenant_id': context.tenant}
  2136. policy.check('create_zone_export', context, target)
  2137. values = {
  2138. 'status': 'PENDING',
  2139. 'message': None,
  2140. 'zone_id': zone_id,
  2141. 'tenant_id': context.tenant,
  2142. 'task_type': 'EXPORT'
  2143. }
  2144. zone_export = objects.ZoneExport(**values)
  2145. created_zone_export = self.storage.create_zone_export(context,
  2146. zone_export)
  2147. if not cfg.CONF['service:worker'].enabled:
  2148. # So that we can maintain asynch behavior during the time that this
  2149. # lives in central, we'll return the 'PENDING' object, and then the
  2150. # 'COMPLETE'/'ERROR' status will be available on the first poll.
  2151. export = copy.deepcopy(created_zone_export)
  2152. synchronous = cfg.CONF['service:zone_manager'].export_synchronous
  2153. criterion = {'zone_id': zone_id}
  2154. count = self.storage.count_recordsets(context, criterion)
  2155. if synchronous:
  2156. try:
  2157. self.quota.limit_check(
  2158. context, context.tenant, api_export_size=count)
  2159. except exceptions.OverQuota:
  2160. LOG.debug('Zone Export too large to perform synchronously')
  2161. export.status = 'ERROR'
  2162. export.message = 'Zone is too large to export'
  2163. return export
  2164. export.location = \
  2165. "designate://v2/zones/tasks/exports/%(eid)s/export" % \
  2166. {'eid': export.id}
  2167. export.status = 'COMPLETE'
  2168. else:
  2169. LOG.debug('No method found to export zone')
  2170. export.status = 'ERROR'
  2171. export.message = 'No suitable method for export'
  2172. self.update_zone_export(context, export)
  2173. else:
  2174. export = copy.deepcopy(created_zone_export)
  2175. self.worker_api.start_zone_export(context, zone, export)
  2176. return created_zone_export
  2177. def find_zone_exports(self, context, criterion=None, marker=None,
  2178. limit=None, sort_key=None, sort_dir=None):
  2179. target = {'tenant_id': context.tenant}
  2180. policy.check('find_zone_exports', context, target)
  2181. criterion = {
  2182. 'task_type': 'EXPORT'
  2183. }
  2184. return self.storage.find_zone_exports(context, criterion, marker,
  2185. limit, sort_key, sort_dir)
  2186. def get_zone_export(self, context, zone_export_id):
  2187. target = {'tenant_id': context.tenant}
  2188. policy.check('get_zone_export', context, target)
  2189. return self.storage.get_zone_export(context, zone_export_id)
  2190. @notification('dns.zone_export.update')
  2191. def update_zone_export(self, context, zone_export):
  2192. target = {
  2193. 'tenant_id': zone_export.tenant_id,
  2194. }
  2195. policy.check('update_zone_export', context, target)
  2196. return self.storage.update_zone_export(context, zone_export)
  2197. @notification('dns.zone_export.delete')
  2198. @transaction
  2199. def delete_zone_export(self, context, zone_export_id):
  2200. target = {
  2201. 'zone_export_id': zone_export_id,
  2202. 'tenant_id': context.tenant
  2203. }
  2204. policy.check('delete_zone_export', context, target)
  2205. zone_export = self.storage.delete_zone_export(context, zone_export_id)
  2206. return zone_export
  2207. def find_service_statuses(self, context, criterion=None, marker=None,
  2208. limit=None, sort_key=None, sort_dir=None):
  2209. """List service statuses.
  2210. """
  2211. policy.check('find_service_statuses', context)
  2212. return self.storage.find_service_statuses(
  2213. context, criterion, marker, limit, sort_key, sort_dir)
  2214. def find_service_status(self, context, criterion=None):
  2215. policy.check('find_service_status', context)
  2216. return self.storage.find_service_status(context, criterion)
  2217. def update_service_status(self, context, service_status):
  2218. policy.check('update_service_status', context)
  2219. criterion = {
  2220. "service_name": service_status.service_name,
  2221. "hostname": service_status.hostname
  2222. }
  2223. if service_status.obj_attr_is_set('id'):
  2224. criterion["id"] = service_status.id
  2225. try:
  2226. db_status = self.storage.find_service_status(
  2227. context, criterion)
  2228. db_status.update(dict(service_status))
  2229. return self.storage.update_service_status(context, db_status)
  2230. except exceptions.ServiceStatusNotFound:
  2231. return self.storage.create_service_status(
  2232. context, service_status)