diff --git a/dyn/__init__.py b/dyn/__init__.py index b9b148e..534c08e 100644 --- a/dyn/__init__.py +++ b/dyn/__init__.py @@ -5,7 +5,7 @@ Requires Python 2.6 or higher, or the "simplejson" package. """ -version_info = (1, 3, 8) +version_info = (2, 0, 0) __name__ = 'dyn' __doc__ = 'A python wrapper for the DynDNS and DynEmail APIs' __author__ = 'Jonathan Nappi, Cole Tuininga' diff --git a/dyn/core.py b/dyn/core.py index e27d62e..f635bff 100644 --- a/dyn/core.py +++ b/dyn/core.py @@ -1,19 +1,36 @@ # -*- coding: utf-8 -*- """dyn.core is a utilities module for use internally within the dyn library itself. Although it's possible to use this functionality outside of the dyn -library, it is not recommened and could possible result in some strange +library, it is not recommened and could possibly result in some strange behavior. """ import copy import time import locale import logging +import warnings import threading from datetime import datetime +from functools import wraps from . import __version__ from .compat import (HTTPConnection, HTTPSConnection, HTTPException, json, - prepare_to_send, force_unicode) + prepare_to_send, force_unicode, string_types) + + +def deprecation_warning(f): + """Log a deprecation warning when a decorated method is called. This + decorator is used to encourage users to steer away from currently supported + deprecated services + """ + @wraps(f) + def inner(*args, **kwargs): + msg = ('WARNING: This DynECT service is now deprecated and will be ' + 'removed in a future version of this library. For information ' + 'on upgrading, please visit http://help.dyn.com/') + warnings.warn(msg, DeprecationWarning, stacklevel=2) + return f(*args, **kwargs) + return inner def cleared_class_dict(dict_obj): @@ -38,6 +55,7 @@ def clean_args(dict_obj): class _Singleton(type): _instances = {} + def __call__(cls, *args, **kwargs): cur_thread = threading.current_thread() key = getattr(cls, '__metakey__') @@ -61,10 +79,246 @@ class Singleton(_Singleton('SingletonMeta', (object,), {})): pass +class APIDescriptor(object): + """A Base descriptor type for attributes of objects being constructed by + API responses + """ + + def __init__(self, name='', default=None): + super(APIDescriptor, self).__init__() + self.name = name + self.private_name = '_' + self.name + self.default = default + + def __get__(self, instance, cls): + """Return a handle on the private instance of this descriptor""" + return getattr(instance, self.private_name, self.default) + + def __set__(self, instance, value): + """Update the value of the private attribute represented by this + descriptor and, if one is available, call the update method of the + instance whose attribute is being updated + """ + setattr(instance, self.private_name, value) + if hasattr(instance, '_update'): + args = {self.name: value} + instance._update(**args) + + +class ImmutableAttribute(APIDescriptor): + """An API Attribute that can not be overridden""" + + def __set__(self, instance, cls): + pass + + +class TypedAttribute(APIDescriptor): + """Type enforced descriptor""" + ty = object + + def __get__(self, instance, owner): + """Force our returned value to be returned as our specified type, but + don't choke and die if something goes wrong + """ + calculated = super(TypedAttribute, self).__get__(instance, owner) + try: + return self.ty(calculated) + except (TypeError, ValueError): + return calculated + + def __set__(self, instance, value): + """Before overwriting *value*, ensure that it is of the correct type""" + if not isinstance(value, self.ty): + raise TypeError('Expected %s' % self.ty) + super(TypedAttribute, self).__set__(instance, value) + + +class IntegerAttribute(TypedAttribute): + """API Attribute that may only be an integer type""" + ty = int + + +class StringAttribute(TypedAttribute): + """API Attribute that may only be a string type""" + ty = string_types + + +class BooleanAttribute(TypedAttribute): + """API Attribute that may only be a string type""" + ty = bool + + +class ListAttribute(TypedAttribute): + """API Attribute that may only be a list""" + ty = list + + +class ClassAttribute(TypedAttribute): + """API Attribute that can force type checking on an arbitrary type. Useful + for API attributes that are wrapped in something else to make them more + easy to use + """ + def __init__(self, name='', default=None, class_type=object): + self.ty = class_type + super(ClassAttribute, self).__init__(name=name, default=default) + + +class ValidatedAttribute(APIDescriptor): + """An API Attribute whose value can be forced to a specific subset of + values + """ + + def __init__(self, name='', default=None, validator=None): + """An API field that must be one of a specific set of values + + :param name: The name of this field + :param validator: An optional list of valid values for this field + """ + super(ValidatedAttribute, self).__init__(name=name, default=default) + self.validator = validator + + def __set__(self, instance, value): + # Get around circular imports by importing here + from dyn.tm.errors import DynectInvalidArgumentError + + if self.validator is not None: + if value in self.validator: + super(ValidatedAttribute, self).__set__(instance, value) + else: + raise DynectInvalidArgumentError(value, self.validator) + else: # If we have no validator, then assume it's safe to overwrite + super(ValidatedAttribute, self).__set__(instance, value) + + +class ValidatedListAttribute(ValidatedAttribute, ListAttribute): + """An atribute type checked as a list, with validators for the individual + items the list contains + """ + + def __set__(self, instance, value): + l = getattr(instance, self.private_name, []) + for item in l: + super(ValidatedListAttribute, self).__set__(instance, item) + setattr(instance, self.private_name, value) + + +# noinspection PyUnusedLocal +class APIObject(object): + """Base API Object type responsible for handling shared functionality + between various objects constructed from what comes out of the API. This + base object has some safe default behavior such as a _build method that + simply takes in api data and appends a '_' to the name before assigning it + as a variable for the instance. A _get method that passes no arguments to + the API. And a delete that passes no arguments to the API. + + This class also provides a __json__ attribute that attempts to generify + the conversion of these Python objects into JSON blobs. By default this + property creates a cleared_class_dict and further filters out any instance + attributes that start with '_' but not '__'. + """ + uri = '' + _get_length = 0 + session_type = None + + def __init__(self, *args, **kwargs): + super(APIObject, self).__init__() + if 'api' in kwargs: + del kwargs['api'] + self._build(kwargs) + elif len(args) + len(kwargs) == self._get_length: + self._get(*args, **kwargs) + else: + self._post(*args, **kwargs) + + def _build(self, data): + """Build the variables in this object by pulling out the data from the + provided data dict + """ + for key, val in data.items(): + setattr(self, '_' + key, val) + + def _post(self, *args, **kwargs): + """POST method, to be inherited by all child classes on a case-by-case + basis + """ + raise NotImplementedError('_post must be overriden by child classes') + + def _get(self, *args, **kwargs): + """Retrieve this object from the DynECT System""" + response = self.session_type.get_session().execute(self.uri, 'GET') + self._build(response['data']) + + def _update(self, **api_args): + """Update this object on the DynECT System""" + response = self.session_type.get_session().execute(self.uri, 'PUT', + api_args) + self._build(response['data']) + + def delete(self, *args, **kwargs): + """Delete this object from the DynECT system""" + if self.session_type is not None: + self.session_type.get_session().execute(self.uri, 'DELETE') + + def to_json(self): + """Generic JSON reprsentation of this object""" + dict_obj = cleared_class_dict(self.__dict__) + return {x: dict_obj[x] for x in dict_obj if x.startswith('_') and + not x.startswith('__')} + + def __str__(self): + return force_unicode('') + + def __repr__(self): + return self.__str__() + + def __unicode__(self): + return self.__str__() + + def __bytes__(self): + return bytes(self.__str__()) + + +class APIService(APIObject): + """:class:`APIObject` subclass with some additional info provided for + services that have "activate" and "deactivate" functionality to simplify + the ability to activate and deactivate the service + """ + + @property + def active(self): + """Indicates if the service is active. When setting directly, rather + than using activate/deactivate valid arguments are 'Y' or True to + activate, or 'N' or False to deactivate. Note: If your service is + already active and you try to activate it, nothing will happen. And + vice versa for deactivation. + + :returns: An :class:`Active` object representing the current state of + this :class:`GSLB` Service + """ + return self.active + @active.setter + def active(self, value): + deactivate = ('N', False) + activate = ('Y', True) + if value in deactivate and self.active: + self.deactivate() + elif value in activate and not self.active: + self.activate() + + def activate(self): + """Ensure that this service is currently activated""" + self._update(activate=True) + + def deactivate(self): + """Ensure that this service is currently deactivated""" + self._update(deactivate=True) + + class _History(list): """A *list* subclass specifically targeted at being able to store the history of calls made via a SessionEngine """ + def append(self, p_object): """Override builtin list append operators to allow for the automatic appendation of a timestamp for cleaner record keeping @@ -73,6 +327,16 @@ def append(self, p_object): super(_History, self).append(tuple([now_ts] + list(p_object))) +class NoActiveSessionError(Exception): + """Custom exception type to be raised if `SessionEngine.get_session` is + called and there is not an active session instance for it to return + """ + def __init__(self, sessiontype): + self.message = ('You must have an active {0} instance to perform this ' + 'operation'.format(str(sessiontype))) + + +# noinspection PyMethodParameters class SessionEngine(Singleton): """Base object representing a DynectSession Session""" _valid_methods = tuple() @@ -122,10 +386,36 @@ def new_session(cls, *args, **kwargs): def get_session(cls): """Return the current session for this Session type or None if there is not an active session + + :raises :class:`NoActiveSessionError` if there is not currently an + active session """ cur_thread = threading.current_thread() key = getattr(cls, '__metakey__') - return cls._instances.get(key, {}).get(cur_thread, None) + instance = cls._instances.get(key, {}).get(cur_thread, None) + if instance is None: + raise NoActiveSessionError(cls) + return instance + + @classmethod + def get(cls, url, api_args=None): + """Wrap a raw session execute call with a GET convenience method""" + return cls.get_session().execute(url, 'GET', api_args) + + @classmethod + def post(cls, url, api_args=None): + """Wrap a raw session execute call with a POST convenience method""" + return cls.get_session().execute(url, 'POST', api_args) + + @classmethod + def put(cls, url, api_args=None): + """Wrap a raw session execute call with a PUT convenience method""" + return cls.get_session().execute(url, 'PUT', api_args) + + @classmethod + def delete(cls, url, api_args=None): + """Wrap a raw session execute call with a DELETE convenience method""" + return cls.get_session().execute(url, 'DELETE', api_args) @classmethod def close_session(cls): @@ -159,14 +449,13 @@ def connect(self): self._token = None self._conn = None if self.ssl: - msg = 'Establishing SSL connection to {}:{}'.format(self.host, - self.port) + msg = 'Establishing SSL connection to {0}:{1}'.format(self.host, + self.port) self.logger.info(msg) self._conn = HTTPSConnection(self.host, self.port, timeout=300) else: - msg = \ - 'Establishing unencrypted connection to {}:{}'.format(self.host, - self.port) + msg = 'Establishing unencrypted connection to {0}:{1}' + msg = msg.format(self.host, self.port) self.logger.info(msg) self._conn = HTTPConnection(self.host, self.port, timeout=300) @@ -182,8 +471,8 @@ def _process_response(self, response, method, final=False): return response def _handle_error(self, uri, method, raw_args): - """Handle the processing of a connection error with the api. Note, to be - implemented as needed in subclasses. + """Handle the processing of a connection error with the api. Note, to + be implemented as needed in subclasses. """ return None @@ -208,6 +497,7 @@ def _handle_response(self, response, uri, method, raw_args, final): time.sleep(8) return self.execute(uri, method, raw_args, final=True) else: + self.logger.debug('response: {0}'.format(ret_val)) return self._process_response(ret_val, method) def _validate_uri(self, uri): @@ -225,7 +515,7 @@ def _validate_uri(self, uri): def _validate_method(self, method): """Validate the provided HTTP method type""" if method.upper() not in self._valid_methods: - msg = '{} is not a valid HTTP method. Please use one of {}' + msg = '{0} is not a valid HTTP method. Please use one of {1}' msg = msg.format(method, ', '.join(self._valid_methods)) raise ValueError(msg) @@ -266,8 +556,9 @@ def execute(self, uri, method, args=None, final=False): # Prepare arguments to send to API raw_args, args, uri = self._prepare_arguments(args, method, uri) - msg = 'uri: {}, method: {}, args: {}' - self.logger.debug(msg.format(uri, method, clean_args(json.loads(args)))) + msg = 'uri: {0}, method: {1}, args: {2}' + self.logger.debug(msg.format(uri, method, + clean_args(json.loads(args)))) # Send the command and deal with results self.send_command(uri, method, args) @@ -316,7 +607,7 @@ def poll_response(self, response, body): while response.status == 307: time.sleep(1) uri = response.getheader('Location') - self.logger.info('Polling {}'.format(uri)) + self.logger.info('Polling {0}'.format(uri)) self.send_command(uri, 'GET', '') response = self._conn.getresponse() @@ -334,7 +625,7 @@ def send_command(self, uri, method, args): self._conn.putrequest(method, uri) # Build headers - user_agent = 'dyn-py v{}'.format(__version__) + user_agent = 'dyn-py v{0}'.format(__version__) headers = {'Content-Type': self.content_type, 'User-Agent': user_agent} for key, val in self.extra_headers.items(): headers[key] = val @@ -360,14 +651,14 @@ def wait_for_job_to_complete(self, job_id, timeout=120): :param timeout: how long (in seconds) we should wait for a valid response before giving up on this request """ - self.logger.debug('Polling for job_id: {}'.format(job_id)) + self.logger.debug('Polling for job_id: {0}'.format(job_id)) start = datetime.now() - uri = '/Job/{}/'.format(job_id) + uri = '/Job/{0}/'.format(job_id) api_args = {} # response = self.execute(uri, 'GET', api_args) response = {'status': 'incomplete'} now = datetime.now() - self.logger.warn('Waiting for job {}'.format(job_id)) + self.logger.warn('Waiting for job {0}'.format(job_id)) too_long = (now - start).seconds < timeout while response['status'] is 'incomplete' and too_long: time.sleep(10) @@ -383,16 +674,17 @@ def __getstate__(cls): return d def __setstate__(cls, state): - """Because the HTTP/HTTPS connection was stripped out in __getstate__ we - must manually re-enter it as None and let the sessions execute method - handle rebuilding it later + """Because the HTTP/HTTPS connection was stripped out in __getstate__ + we must manually re-enter it as None and let the sessions execute + method handle rebuilding it later """ cls.__dict__ = state cls.__dict__['_conn'] = None def __str__(self): """str override""" - return force_unicode('<{}>').format(self.name) + return force_unicode('<{0}>').format(self.name) + __repr__ = __unicode__ = __str__ def __bytes__(self): diff --git a/dyn/tm/accounts.py b/dyn/tm/accounts.py index 75503d5..9d7edb2 100644 --- a/dyn/tm/accounts.py +++ b/dyn/tm/accounts.py @@ -2,33 +2,34 @@ """This module contains interfaces for all Account management features of the REST API """ -import logging - from .errors import DynectInvalidArgumentError -from .session import DynectSession +from .session import DynectSession, DNSAPIObject +from ..core import (ImmutableAttribute, StringAttribute, ListAttribute, + ValidatedAttribute) from ..compat import force_unicode __author__ = 'jnappi' __all__ = ['get_updateusers', 'get_users', 'get_permissions_groups', 'get_contacts', 'get_notifiers', 'UpdateUser', 'User', - 'PermissionsGroup', 'UserZone', 'Notifier', 'Contact'] + 'PermissionsGroup', 'Notifier', 'Contact'] def get_updateusers(search=None): - """Return a ``list`` of :class:`~dyn.tm.accounts.UpdateUser` objects. If - *search* is specified, then only :class:`~dyn.tm.accounts.UpdateUsers` who - match those search criteria will be returned in the list. Otherwise, all - :class:`~dyn.tm.accounts.UpdateUsers`'s will be returned. - - :param search: A ``dict`` of search criteria. Key's in this ``dict`` much - map to an attribute a :class:`~dyn.tm.accounts.UpdateUsers` instance - and the value mapped to by that key will be used as the search criteria - for that key when searching. - :return: a ``list`` of :class:`~dyn.tm.accounts.UpdateUser` objects + """Return a :const:`list` of :class:`~dyn.tm.accounts.UpdateUser` objects. + If *search* is specified, then only :class:`~dyn.tm.accounts.UpdateUsers` + who match those search criteria will be returned in the list. Otherwise, + all :class:`~dyn.tm.accounts.UpdateUsers`'s will be returned. + + :param search: A :const:`dict` of search criteria. Key's in this + :const:`dict` must map to an attribute a + :class:`~dyn.tm.accounts.UpdateUsers` instance and the value mapped to + by that key will be used as the search criteria for that key when + searching. + :return: a :const:`list` of :class:`~dyn.tm.accounts.UpdateUser` objects """ uri = '/UpdateUser/' api_args = {'detail': 'Y'} - response = DynectSession.get_session().execute(uri, 'GET', api_args) + response = DynectSession.get(uri, api_args) update_users = [] for user in response['data']: update_users.append(UpdateUser(api=False, **user)) @@ -43,16 +44,16 @@ def get_updateusers(search=None): def get_users(search=None): - """Return a ``list`` of :class:`~dyn.tm.accounts.User` objects. If *search* - is specified, then only users who match those search parameters will be - returned in the list. Otherwise, all :class:`~dyn.tm.accounts.User`'s will - be returned. - - :param search: A ``dict`` of search criteria. Key's in this ``dict`` much - map to an attribute a :class:`~dyn.tm.accounts.User` instance and the - value mapped to by that key will be used as the search criteria for - that key when searching. - :return: a ``list`` of :class:`~dyn.tm.accounts.User` objects + """Return a :const:`list` of :class:`~dyn.tm.accounts.User` objects. If + *search* is specified, then only users who match those search parameters + will be returned in the list. Otherwise, all + :class:`~dyn.tm.accounts.User`'s will be returned. + + :param search: A :const:`dict` of search criteria. Key's in this + :const:`dict` must map to an attribute a :class:`~dyn.tm.accounts.User` + instance and the value mapped to by that key will be used as the search + criteria for that key when searching. + :return: a :const:`list` of :class:`~dyn.tm.accounts.User` objects """ uri = '/User/' api_args = {'detail': 'Y'} @@ -64,7 +65,7 @@ def get_users(search=None): else: search_string = '{}:"{}"'.format(key, val) api_args['search'] = search_string - response = DynectSession.get_session().execute(uri, 'GET', api_args) + response = DynectSession.get(uri, api_args) users = [] for user in response['data']: user_name = None @@ -76,20 +77,23 @@ def get_users(search=None): def get_permissions_groups(search=None): - """Return a ``list`` of :class:`~dyn.tm.accounts.PermissionGroup` objects. - If *search* is specified, then only + """Return a :const:`list` of :class:`~dyn.tm.accounts.PermissionGroup` + objects. If *search* is specified, then only :class:`~dyn.tm.accounts.PermissionGroup`'s that match those search criteria will be returned in the list. Otherwise, all :class:`~dyn.tm.accounts.PermissionGroup`'s will be returned. - :param search: A ``dict`` of search criteria. Key's in this ``dict`` much - map to an attribute a :class:`~dyn.tm.accounts.PermissionGroup` - instance and the value mapped to by that key will be used as the search - criteria for that key when searching. - :return: a ``list`` of :class:`~dyn.tm.accounts.PermissionGroup` objects""" + :param search: A :const:`dict` of search criteria. Key's in this + :const:`dict` must map to an attribute a + :class:`~dyn.tm.accounts.PermissionGroup` instance and the value mapped + to by that key will be used as the search criteria for that key when + searching. + :return: a :const:`list` of :class:`~dyn.tm.accounts.PermissionGroup` + objects + """ uri = '/PermissionGroup/' api_args = {'detail': 'Y'} - response = DynectSession.get_session().execute(uri, 'GET', api_args) + response = DynectSession.get(uri, api_args) groups = [] for group in response['data']: groups.append(PermissionsGroup(None, api=False, **group)) @@ -104,19 +108,21 @@ def get_permissions_groups(search=None): def get_contacts(search=None): - """Return a ``list`` of :class:`~dyn.tm.accounts.Contact` objects. If + """Return a `:const:`list` of :class:`~dyn.tm.accounts.Contact` objects. If *search* is specified, then only :class:`~dyn.tm.accounts.Contact`'s who match those search criteria will be returned in the list. Otherwise, all :class:`~dyn.tm.accounts.Contact`'s will be returned. - :param search: A ``dict`` of search criteria. Key's in this ``dict`` much - map to an attribute a :class:`~dyn.tm.accounts.Contact` instance and - the value mapped to by that key will be used as the search criteria - for that key when searching. - :return: a ``list`` of :class:`~dyn.tm.accounts.Contact` objects""" + :param search: A :const:`dict` of search criteria. Key's in this + :const:`dict` must map to an attribute a + :class:`~dyn.tm.accounts.Contact` instance and the value mapped to by + that key will be used as the search criteria for that key when + searching. + :return: a :const:`list` of :class:`~dyn.tm.accounts.Contact` objects + """ uri = '/Contact/' api_args = {'detail': 'Y'} - response = DynectSession.get_session().execute(uri, 'GET', api_args) + response = DynectSession.get(uri, api_args) contacts = [] for contact in response['data']: if 'nickname' in contact: @@ -134,19 +140,21 @@ def get_contacts(search=None): def get_notifiers(search=None): - """Return a ``list`` of :class:`~dyn.tm.accounts.Notifier` objects. If + """Return a :const:`list` of :class:`~dyn.tm.accounts.Notifier` objects. If *search* is specified, then only :class:`~dyn.tm.accounts.Notifier`'s who match those search criteria will be returned in the list. Otherwise, all :class:`~dyn.tm.accounts.Notifier`'s will be returned. - :param search: A ``dict`` of search criteria. Key's in this ``dict`` much - map to an attribute a :class:`~dyn.tm.accounts.Notifier` instance and - the value mapped to by that key will be used as the search criteria for - that key when searching. - :return: a ``list`` of :class:`~dyn.tm.accounts.Notifier` objects""" + :param search: A :const:`dict` of search criteria. Key's in this + :const:`dict` must map to an attribute a + :class:`~dyn.tm.accounts.Notifier` instance and the value mapped to by + that key will be used as the search criteria for that key when + searching. + :return: a :const:`list` of :class:`~dyn.tm.accounts.Notifier` objects + """ uri = '/Notifier/' api_args = {'detail': 'Y'} - response = DynectSession.get_session().execute(uri, 'GET', api_args) + response = DynectSession.get(uri, api_args) notifiers = [] for notifier in response['data']: notifiers.append(Notifier(None, api=False, **notifier)) @@ -160,143 +168,66 @@ def get_notifiers(search=None): return notifiers -class UpdateUser(object): +class UpdateUser(DNSAPIObject): """:class:`~dyn.tm.accounts.UpdateUser` type objects are a special form of a :class:`~dyn.tm.accounts.User` which are tied to a specific Dynamic DNS services. """ + _get_length = 1 + + #: UpdateUser URI + uri = '/UpdateUser/' + + #: This UpdateUser's user_name. An UpdateUser's user_name is a read-only + #: property which can not be updated after the UpdateUser has been created. + user_name = ImmutableAttribute('user_name') + + #: The current password for this UpdateUser. This password may be updated. + password = StringAttribute('password') + + #: This UpdateUser's nickname. An UpdateUser's nickname is a read-only + #: property which can not be updated after the UpdateUser has been created. + nickname = StringAttribute('nickname') + + #: The current status of this UpdateUser will be one of either 'active' or + #: 'blocked'. Blocked UpdateUser's are unable to log into the DynECT + #: System, while active UpdateUser's are. + status = ImmutableAttribute('status') + def __init__(self, *args, **kwargs): - """Create an :class:`~dyn.tm.accounts.UpdateUser` object - - :param user_name: the Username this - :class:`~dyn.tm.accounts.UpdateUser` uses or will use to log in to - the DynECT System. A :class:`~dyn.tm.accounts.UpdateUser`'s - `user_name` is required for both creating and getting - :class:`~dyn.tm.accounts.UpdateUser`'s. - :param nickname: When creating a new - :class:`~dyn.tm.accounts.UpdateUser` on the DynECT System, this - `nickname` will be the System nickname for this - :class:`~dyn.tm.accounts.UpdateUser` - :param password: When creating a new - :class:`~dyn.tm.accounts.UpdateUser` on the DynECT System, this - `password` will be the password this - :class:`~dyn.tm.accounts.UpdateUser` uses to log into the System - """ - super(UpdateUser, self).__init__() - self.uri = '/UpdateUser/' - self._password = self._status = self._user_name = self._nickname = None - if 'api' in kwargs: - good_args = ('user_name', 'status', 'password') - for key, val in kwargs.items(): - if key in good_args: - setattr(self, '_' + key, val) - self.uri = '/UpdateUser/{}/'.format(self._user_name) - elif len(args) + len(kwargs) == 1: - self._get(*args, **kwargs) - else: - self._post(*args, **kwargs) + super(UpdateUser, self).__init__(*args, **kwargs) + self.uri = '/UpdateUser/{0}/'.format(self._user_name) def _post(self, nickname, password): """Create a new :class:`~dyn.tm.accounts.UpdateUser` on the DynECT System """ - self._nickname = nickname - self._password = password - uri = '/UpdateUser/' - api_args = {'nickname': self._nickname, - 'password': self._password} - response = DynectSession.get_session().execute(uri, 'POST', api_args) + api_args = {'nickname': nickname, 'password': password} + response = DynectSession.post(self.uri, api_args) self._build(response['data']) - self.uri = '/UpdateUser/{}/'.format(self._user_name) def _get(self, user_name): """Get an existing :class:`~dyn.tm.accounts.UpdateUser` from the DynECT System """ self._user_name = user_name - self.uri = '/UpdateUser/{}/'.format(self._user_name) - response = DynectSession.get_session().execute(self.uri, 'GET') - self._build(response['data']) - - def _build(self, data): - for key, val in data.items(): - setattr(self, '_' + key, val) - - def _update(self, api_args=None): - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) + self.uri = '/UpdateUser/{0}/'.format(user_name) + response = DynectSession.get(self.uri) self._build(response['data']) - @property - def user_name(self): - """This :class:`~dyn.tm.accounts.UpdateUser`'s `user_name`. An - :class:`~dyn.tm.accounts.UpdateUser`'s user_name is a read-only - property which can not be updated after the :class:`UpdateUser` has - been created. - """ - return self._user_name - @user_name.setter - def user_name(self, value): - pass - - @property - def nickname(self): - """This :class:`~dyn.tm.accounts.UpdateUser`s `nickname`. An - :class:`~dyn.tm.accounts.UpdateUser`'s `nickname` is a read-only - property which can not be updated after the - :class:`~dyn.tm.accounts.UpdateUser` has been created. - """ - return self._nickname - @nickname.setter - def nickname(self, value): - pass - - @property - def status(self): - """The current `status` of an :class:`~dyn.tm.accounts.UpdateUser` will - be one of either 'active' or 'blocked'. Blocked - :class:`~dyn.tm.accounts.UpdateUser`'s are unable to log into the - DynECT System, where active :class:`~dyn.tm.accounts.UpdateUser`'s are. - """ - return self._status - @status.setter - def status(self, value): - pass - - @property - def password(self): - """The current `password` for this - :class:`~dyn.tm.accounts.UpdateUser`. An - :class:`~dyn.tm.accounts.UpdateUser`'s `password` may be reassigned. - """ - if self._password is None or self._password == u'': - self._get(self._user_name) - return self._password - @password.setter - def password(self, new_password): - """Update this :class:`~dyn.tm.accounts.UpdateUser`'s password to be - the provided password - - :param new_password: The new password to use - """ - api_args = {'password': new_password} - self._update(api_args) - def block(self): """Set the status of this :class:`~dyn.tm.accounts.UpdateUser` to 'blocked'. This will prevent this :class:`~dyn.tm.accounts.UpdateUser` from logging in until they are explicitly unblocked. """ - api_args = {'block': True} - self._update(api_args) + self._update(block=True) def unblock(self): """Set the status of this :class:`~dyn.tm.accounts.UpdateUser` to 'active'. This will re-enable this :class:`~dyn.tm.accounts.UpdateUser` to be able to login if they were previously blocked. """ - api_args = {'unblock': True} - self._update(api_args) + self._update(unblock=True) def sync_password(self): """Pull in this :class:`~dyn.tm.accounts.UpdateUser` current password @@ -304,27 +235,92 @@ def sync_password(self): :class:`~dyn.tm.accounts.UpdateUser` object's password may have gotten out of sync """ - api_args = {'user_name': self._user_name} - self._update(api_args) + self._update(user_name=self._user_name) def delete(self): """Delete this :class:`~dyn.tm.accounts.UpdateUser` from the DynECT - System. It is important to note that this operation may not be undone. + System. It is important to note that this operation can not be undone. """ - DynectSession.get_session().execute(self.uri, 'DELETE') + DynectSession.delete(self.uri) def __str__(self): - """Custom str method""" - return force_unicode(': {}').format(self.user_name) - __repr__ = __unicode__ = __str__ - - def __bytes__(self): - """bytes override""" - return bytes(self.__str__()) + return force_unicode(': {0}').format(self.user_name) -class User(object): +# noinspection PyAttributeOutsideInit,PyUnresolvedReferences +class User(DNSAPIObject): """DynECT System User object""" + #: DynECT User URI + uri = '/User/{user_name}/' + + #: This User's user_name. This is a read-only property. + user_name = ImmutableAttribute('user_name') + + #: The first name of this User + first_name = StringAttribute('first_name') + + #: The last name of this User + last_name = StringAttribute('last_name') + + #: The nickname asociated with this User + nickname = StringAttribute('nickname') + + #: This User's Email address + email = StringAttribute('email') + + #: The password for this user + password = StringAttribute('password') + + #: The organization this User belongs to + organization = StringAttribute('organization') + + #: The primary phone number for this User + phone = StringAttribute('phone') + + #: The primary address of this User + address = StringAttribute('address') + + #: The primary address of this User, line 2 + address_2 = StringAttribute('address_2') + + #: The city this User is from + city = StringAttribute('city') + + #: The country this User is from + country = StringAttribute('country') + + #: The fax number associated with this User + fax = StringAttribute('fax') + + #: The primary notification email for this User + notify_email = StringAttribute('notify_email') + + #: The primary pager email for this User + pager_email = StringAttribute('pager_email') + + #: This User's zip code + post_code = StringAttribute('post_code') + + #: The name of the Group that this User belongs to + group_name = StringAttribute('group_name') + + #: The permissions associated with this User + permision = StringAttribute('permission') + + #: The zones that this User has access to + zone = StringAttribute('zone') + + #: The zones that this User is explicitly forbidden from + forbid = StringAttribute('forbid') + + #: A website associated with this User + website = StringAttribute('website') + + #: The current status of this User. Note, although this is a read only + #: property, it may be indirectly set by using the :meth:`block` and + #: :meth:`unblock` methods + status = ImmutableAttribute('status') + def __init__(self, user_name, *args, **kwargs): """Create a new :class:`~dyn.tm.accounts.User` object @@ -357,40 +353,22 @@ def __init__(self, user_name, *args, **kwargs): :class:`~dyn.tm.accounts.User` should receive messages destined for a pager :param post_code: Zip code or Postal code - :param group_name: A list of permission groups this + :param group_name: A :const:`list` of permission groups this :class:`~dyn.tm.accounts.User` belongs to - :param permission: A list of permissions assigned to this + :param permission: A :const:`list` of permissions assigned to this :class:`~dyn.tm.accounts.User` - :param zone: A list of zones where this + :param zone: A :const:`list` of zones where this :class:`~dyn.tm.accounts.User`'s permissions apply - :param forbid: A list of forbidden permissions for this + :param forbid: A :const:`list` of forbidden permissions for this :class:`~dyn.tm.accounts.User` :param status: Current status of this :class:`~dyn.tm.accounts.User` :param website: This :class:`~dyn.tm.accounts.User`'s website """ - super(User, self).__init__() - self._user_name = user_name - self.uri = '/User/{}/'.format(self._user_name) - self._password = self._email = self._first_name = self._last_name = None - self._nickname = self._organization = self._phone = self._address = None - self._address_2 = self._city = self._country = self._fax = None - self._notify_email = self._pager_email = self._post_code = None - self._group_name = self._permission = self._zone = self._forbid = None - self._status = self._website = None + self.uri = self.uri.format(user_name=user_name) self.permissions = [] self.permission_groups = [] self.groups = [] - if 'api' in kwargs: - del kwargs['api'] - for key, val in kwargs.items(): - if key != '_user_name': - setattr(self, '_' + key, val) - else: - setattr(self, key, val) - elif len(args) == 0 and len(kwargs) == 0: - self._get() - else: - self._post(*args, **kwargs) + super(User, self).__init__(*args, **kwargs) def _post(self, password, email, first_name, last_name, nickname, organization, phone, address=None, address_2=None, city=None, @@ -421,258 +399,18 @@ def _post(self, password, email, first_name, last_name, nickname, self._forbid = forbid self._status = status self._website = website - response = DynectSession.get_session().execute(self.uri, 'POST', self) - for key, val in response['data'].items(): - setattr(self, '_' + key, val) - - def _get(self): - """Get an existing :class:`~dyn.tm.accounts.User` object from the - DynECT System - """ - api_args = {} - response = DynectSession.get_session().execute(self.uri, 'GET', - api_args) - for key, val in response['data'].items(): - setattr(self, '_' + key, val) - - def _update(self, api_args=None): - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - for key, val in response['data'].items(): - setattr(self, '_' + key, val) - - @property - def user_name(self): - """A :class:`~dyn.tm.accounts.User`'s user_name is a read-only property - """ - return self._user_name - @user_name.setter - def user_name(self, value): - pass - - @property - def status(self): - """A :class:`~dyn.tm.accounts.User`'s status is a read-only property. - To change you must use the :meth:`block`/:meth:`unblock` methods - """ - return self._status - @status.setter - def status(self, value): - pass - - @property - def email(self): - """This :class:`~dyn.tm.accounts.User`'s Email address""" - return self._email - @email.setter - def email(self, value): - api_args = {'email': value} - self._update(api_args) - - @property - def first_name(self): - """This :class:`~dyn.tm.accounts.User`'s first name""" - return self._first_name - @first_name.setter - def first_name(self, value): - api_args = {'first_name': value} - self._update(api_args) - - @property - def last_name(self): - """This :class:`~dyn.tm.accounts.User`'s last name""" - return self._last_name - @last_name.setter - def last_name(self, value): - api_args = {'last_name': value} - self._update(api_args) - - @property - def nickname(self): - """The nickname for the `Contact` associated with this - :class:`~dyn.tm.accounts.User`""" - return self._nickname - @nickname.setter - def nickname(self, value): - api_args = {'nickname': value} - self._update(api_args) - - @property - def organization(self): - """This :class:`~dyn.tm.accounts.User`'s organization""" - return self._organization - @organization.setter - def organization(self, value): - api_args = {'organization': value} - self._update(api_args) - - @property - def phone(self): - """This :class:`~dyn.tm.accounts.User`'s phone number. Can be of the - form: (0) ( country-code ) ( local number ) ( extension ) Only the - country-code (1-3 digits) and local number (at least 7 digits) are - required. The extension can be up to 4 digits. Any non-digits are - ignored. - """ - return self._phone - @phone.setter - def phone(self, value): - api_args = {'phone': value} - self._update(api_args) - - @property - def address(self): - """This :class:`~dyn.tm.accounts.User`'s street address""" - return self._address - @address.setter - def address(self, value): - api_args = {'address': value} - self._update(api_args) - - @property - def address_2(self): - """This :class:`~dyn.tm.accounts.User`'s street address, line 2""" - return self._address_2 - @address_2.setter - def address_2(self, value): - api_args = {'address_2': value} - self._update(api_args) - - @property - def city(self): - """This :class:`~dyn.tm.accounts.User`'s city, part of the user's - address - """ - return self._city - @city.setter - def city(self, value): - api_args = {'city': value} - self._update(api_args) - - @property - def country(self): - """This :class:`~dyn.tm.accounts.User`'s country, part of the user's - address - """ - return self._country - @country.setter - def country(self, value): - api_args = {'country': value} - self._update(api_args) - - @property - def fax(self): - """This :class:`~dyn.tm.accounts.User`'s fax number""" - return self._fax - @fax.setter - def fax(self, value): - api_args = {'fax': value} - self._update(api_args) - - @property - def notify_email(self): - """Email address where this :class:`~dyn.tm.accounts.User` should - receive notifications - """ - return self._notify_email - @notify_email.setter - def notify_email(self, value): - api_args = {'notify_email': value} - self._update(api_args) - - @property - def pager_email(self): - """Email address where this :class:`~dyn.tm.accounts.User` should - receive messages destined for a pager - """ - return self._pager_email - @pager_email.setter - def pager_email(self, value): - api_args = {'pager_email': value} - self._update(api_args) - - @property - def post_code(self): - """This :class:`~dyn.tm.accounts.User`'s postal code, part of the - user's address - """ - return self._post_code - @post_code.setter - def post_code(self, value): - api_args = {'post_code': value} - self._update(api_args) - - @property - def group_name(self): - """A list of permission groups this :class:`~dyn.tm.accounts.User` - belongs to - """ - return self._group_name - @group_name.setter - def group_name(self, value): - api_args = {'group_name': value} - self._update(api_args) - - @property - def permission(self): - """A list of permissions assigned to this - :class:`~dyn.tm.accounts.User` - """ - return self._permission - @permission.setter - def permission(self, value): - api_args = {'permission': value} - self._update(api_args) - - @property - def zone(self): - """A list of zones where this :class:`~dyn.tm.accounts.User`'s - permissions apply - """ - return self._zone - @zone.setter - def zone(self, value): - api_args = {'zone': value} - self._update(api_args) - - @property - def forbid(self): - """A list of forbidden permissions for this - :class:`~dyn.tm.accounts.User` - """ - return self._forbid - @forbid.setter - def forbid(self, value): - """Apply a new list of forbidden permissions for the - :class:`~dyn.tm.accounts.User` - """ - api_args = {'forbid': value} - self._update(api_args) - - @property - def website(self): - """This :class:`~dyn.tm.accounts.User`'s website""" - return self._website - @website.setter - def website(self, value): - api_args = {'website': value} - self._update(api_args) + response = DynectSession.post(self.uri, self) + self._build(response['data']) def block(self): """Blocks this :class:`~dyn.tm.accounts.User` from logging in""" - api_args = {'block': 'True'} - uri = '/User/{}/'.format(self._user_name) - response = DynectSession.get_session().execute(uri, 'PUT', api_args) - self._status = response['data']['status'] + self._update(block=True) def unblock(self): """Restores this :class:`~dyn.tm.accounts.User` to an active status and re-enables their log-in """ - api_args = {'unblock': 'True'} - uri = '/User/{}/'.format(self._user_name) - response = DynectSession.get_session().execute(uri, 'PUT', api_args) - self._status = response['data']['status'] + self._update(unblock=True) def add_permission(self, permission): """Add individual permissions to this :class:`~dyn.tm.accounts.User` @@ -680,16 +418,17 @@ def add_permission(self, permission): :param permission: the permission to add """ self.permissions.append(permission) - uri = '/UserPermissionEntry/{}/{}/'.format(self._user_name, permission) - DynectSession.get_session().execute(uri, 'POST') + uri = '/UserPermissionEntry/{0}/{1}/'.format(self._user_name, + permission) + DynectSession.post(uri) def replace_permissions(self, permissions=None): - """Replaces the list of permissions for this + """Replaces the :const:`list` of permissions for this :class:`~dyn.tm.accounts.User` - :param permissions: A list of permissions. Pass an empty list or omit - the argument to clear the list of permissions of the - :class:`~dyn.tm.accounts.User` + :param permissions: A :const:`list` of permissions. Pass an empty + :const:`list` or omit the argument to clear the :const:`list` of + permissions of the :class:`~dyn.tm.accounts.User` """ api_args = {} if permissions is not None: @@ -698,7 +437,7 @@ def replace_permissions(self, permissions=None): else: self.permissions = [] uri = '/UserPermissionEntry/{}/'.format(self._user_name) - DynectSession.get_session().execute(uri, 'PUT', api_args) + DynectSession.put(uri, api_args) def delete_permission(self, permission): """Remove this specific permission from the @@ -709,7 +448,7 @@ def delete_permission(self, permission): if permission in self.permissions: self.permissions.remove(permission) uri = '/UserPermissionEntry/{}/{}/'.format(self._user_name, permission) - DynectSession.get_session().execute(uri, 'DELETE') + DynectSession.delete(uri) def add_permissions_group(self, group): """Assigns the permissions group to this :class:`~dyn.tm.accounts.User` @@ -718,16 +457,16 @@ def add_permissions_group(self, group): :class:`~dyn.tm.accounts.User` """ self.permission_groups.append(group) - uri = '/UserGroupEntry/{}/{}/'.format(self._user_name, group) - DynectSession.get_session().execute(uri, 'POST') + uri = '/UserGroupEntry/{0}/{1}/'.format(self._user_name, group) + DynectSession.post(uri) def replace_permissions_group(self, groups=None): - """Replaces the list of permissions for this + """Replaces the :const:`list` of permissions for this :class:`~dyn.tm.accounts.User` - :param groups: A list of permissions groups. Pass an empty list or omit - the argument to clear the list of permissions groups of the - :class:`~dyn.tm.accounts.User` + :param groups: A :const:`list` of permissions groups. Pass an empty + :const:`list` or omit the argument to clear the :const:`list` of + permissions groups of the :class:`~dyn.tm.accounts.User` """ api_args = {} if groups is not None: @@ -735,8 +474,8 @@ def replace_permissions_group(self, groups=None): self.groups = groups else: self.groups = [] - uri = '/UserGroupEntry/{}/'.format(self._user_name) - DynectSession.get_session().execute(uri, 'PUT', api_args) + uri = '/UserGroupEntry/{0}/'.format(self._user_name) + DynectSession.put(uri, api_args) def delete_permissions_group(self, group): """Removes the permissions group from the @@ -748,7 +487,7 @@ def delete_permissions_group(self, group): if group in self.permissions: self.permission_groups.remove(group) uri = '/UserGroupEntry/{}/{}/'.format(self._user_name, group) - DynectSession.get_session().execute(uri, 'DELETE') + DynectSession.delete(uri) def add_forbid_rule(self, permission, zone=None): """Adds the forbid rule to the :class:`~dyn.tm.accounts.User`'s @@ -756,59 +495,79 @@ def add_forbid_rule(self, permission, zone=None): :param permission: the permission to forbid from this :class:`~dyn.tm.accounts.User` - :param zone: A list of zones where the forbid rule applies + :param zone: A :const:`list` of zones where the forbid rule applies """ api_args = {} if zone is not None: api_args['zone'] = zone - uri = '/UserForbidEntry/{}/{}/'.format(self._user_name, permission) - DynectSession.get_session().execute(uri, 'POST', api_args) + uri = '/UserForbidEntry/{0}/{1}/'.format(self._user_name, permission) + DynectSession.post(uri, api_args) def replace_forbid_rules(self, forbid=None): - """Replaces the list of forbidden permissions in the + """Replaces the :const:`list` of forbidden permissions in the :class:`~dyn.tm.accounts.User`'s permissions group with a new list. - :param forbid: A list of rules to replace the forbidden rules on the - :class:`~dyn.tm.accounts.User`'s permission group. If empty or not - passed in, the :class:`~dyn.tm.accounts.User`'s forbid list will be - cleared + :param forbid: A :const:`list` of rules to replace the forbidden rules + on the :class:`~dyn.tm.accounts.User`'s permission group. If empty + or not passed in, the :class:`~dyn.tm.accounts.User`'s forbid + :const:`list` will be cleared """ api_args = {} if forbid is not None: api_args['forbid'] = forbid - uri = '/UserForbidEntry/{}/'.format(self._user_name) - DynectSession.get_session().execute(uri, 'PUT', api_args) + uri = '/UserForbidEntry/{0}/'.format(self._user_name) + DynectSession.put(uri, api_args) def delete_forbid_rule(self, permission, zone=None): """Removes a forbid permissions rule from the :class:`~dyn.tm.accounts.User`'s permission group :param permission: permission - :param zone: A list of zones where the forbid rule applies + :param zone: A :const:`list` of zones where the forbid rule applies """ api_args = {} if zone is not None: api_args['zone'] = zone - uri = '/UserForbidEntry/{}/{}/'.format(self._user_name, permission) - DynectSession.get_session().execute(uri, 'DELETE', api_args) - - def delete(self): - """Delete this :class:`~dyn.tm.accounts.User` from the system""" - uri = '/User/{}/'.format(self._user_name) - DynectSession.get_session().execute(uri, 'DELETE') + uri = '/UserForbidEntry/{0}/{1}/'.format(self._user_name, permission) + DynectSession.delete(uri, api_args) def __str__(self): - """Custom str method""" - return force_unicode(': {}').format(self.user_name) - __repr__ = __unicode__ = __str__ + return force_unicode(': {0}').format(self.user_name) - def __bytes__(self): - """bytes override""" - return bytes(self.__str__()) - -class PermissionsGroup(object): +class PermissionsGroup(DNSAPIObject): """A DynECT System Permissions Group object""" + #: The DynECT Permissions Group URI + uri = '/PermissionGroup/{group_name}/' + + #: The name of this Permissions Group + group_name = StringAttribute('group_name') + + #: A description of this Permissions Group + description = StringAttribute('description') + + #: The type of this Permissions Group. Valid values are plain and default + group_type = ValidatedAttribute('group_type', + validator=('plain', 'default')) + + #: If 'Y', all current users will be added to the group. Cannot be used if + #: user_name is passed in + all_users = StringAttribute('all_users') + + #: A :const:`list` of permissions to apply to this Permissions Group + permission = ListAttribute('permission') + + #: A :const:`list` of users who belong to this Permissions Group + user_name = StringAttribute('user_name') + + #: A :const:`list` of Permissions Group's that belong to this Permissions + #: Group + subgroup = StringAttribute('subgroup') + + #: A :const:`list` of zones where this Permissions Group's permissions + #: apply + zone = ListAttribute('zone') + def __init__(self, group_name, *args, **kwargs): """Create a new permissions Group @@ -818,24 +577,18 @@ def __init__(self, group_name, *args, **kwargs): plain or default :param all_users: If 'Y', all current users will be added to the group. Cannot be used if user_name is passed in - :param permission: A list of permissions that the group contains - :param user_name: A list of users that belong to the permission group - :param subgroup: A list of groups that belong to the permission group - :param zone: A list of zones where the group's permissions apply - """ - super(PermissionsGroup, self).__init__() + :param permission: A :const:`list` of permissions that the group + contains + :param user_name: A :const:`list` of users that belong to the + permission group + :param subgroup: A :const:`list` of groups that belong to the + permission group + :param zone: A :const:`list` of zones where the group's permissions + apply + """ + self.uri = self.uri.format(group_name=group_name) self._group_name = group_name - self._description = self._group_type = self._all_users = None - self._permission = self._user_name = self._subgroup = self._zone = None - self.uri = '/PermissionGroup/{}/'.format(self._group_name) - if 'api' in kwargs: - del kwargs['api'] - for key, val in kwargs.items(): - setattr(self, '_' + key, val) - elif len(args) == 0 and len(kwargs) == 0: - self._get() - else: - self._post(*args, **kwargs) + super(PermissionsGroup, self).__init__(*args, **kwargs) def _post(self, description, group_type=None, all_users=None, permission=None, user_name=None, subgroup=None, zone=None): @@ -858,164 +611,58 @@ def _post(self, description, group_type=None, all_users=None, api_args['type'] = val else: api_args[key[1:]] = val - uri = '/PermissionGroup/{}/'.format(self._group_name) - response = DynectSession.get_session().execute(uri, 'POST', api_args) - for key, val in response['data'].items(): - if key == 'type': - setattr(self, '_group_type', val) - elif key == 'zone': - self._zone = [] - for zone in val: - self._zone.append(zone['zone_name']) - else: - setattr(self, '_' + key, val) + response = DynectSession.post(self.uri, api_args) + self._build(response['data']) + + def _build(self, data): + """Build the variables in this object by pulling out the data from the + provided data dict. This overrided method pulls out special keys from + the dict, prior to passing off the remaining data to super()._build + """ + setattr(self, '_group_type', data.pop('type')) + zones = data.pop('zone') + self._zone = [] + for zone in zones: + self._zone.append(zone['zone_name']) + super(PermissionsGroup, self)._build(data) + self.uri = '/PermissionGroup/{0}/'.format(self.group_name) def _get(self): """Get an existing :class:`~dyn.tm.accounts.PermissionsGroup` from the DynECT System """ - response = DynectSession.get_session().execute(self.uri, 'GET') - for key, val in response['data'].items(): - if key == 'type': - setattr(self, '_group_type', val) - elif key == 'zone': - self._zone = [] - for zone in val: - self._zone.append(zone['zone_name']) - else: - setattr(self, '_' + key, val) - - def _update(self, api_args=None): - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - for key, val in response['data'].items(): - if key == 'type': - setattr(self, '_group_type', val) - elif key == 'zone': - self._zone = [] - for zone in val: - self._zone.append(zone['zone_name']) - else: - setattr(self, '_' + key, val) - - @property - def group_name(self): - """The name of this permission group""" - return self._group_name - @group_name.setter - def group_name(self, value): - new_group_name = value - api_args = {'new_group_name': new_group_name, - 'group_name': self._group_name} - self._update(api_args) - self._group_name = new_group_name - self.uri = '/PermissionGroup/{}/'.format(self._group_name) - - @property - def description(self): - """A description of this permission group""" - return self._description - @description.setter - def description(self, value): - self._description = value - api_args = {'group_name': self._group_name, - 'description': self._description} - self._update(api_args) - - @property - def group_type(self): - """The type of this permission group""" - return self._group_type - @group_type.setter - def group_type(self, value): - self._group_type = value - api_args = {'type': self._group_type, - 'group_name': self._group_name} - self._update(api_args) - - @property - def all_users(self): - """If 'Y', all current users will be added to the group. Cannot be - used if user_name is passed in - """ - return self._all_users - @all_users.setter - def all_users(self, value): - self._all_users = value - api_args = {'all_users': self._all_users, - 'group_name': self._group_name} - self._update(api_args) - - @property - def permission(self): - """A list of permissions that this group contains""" - return self._permission - @permission.setter - def permission(self, value): - self._permission = value - api_args = {'permission': self._permission, - 'group_name': self._group_name} - self._update(api_args) - - @property - def user_name(self): - """A list of users that belong to the permission group""" - return self._user_name - @user_name.setter - def user_name(self, value): - self._user_name = value - api_args = {'user_name': self._user_name, - 'group_name': self._group_name} - self._update(api_args) - - @property - def subgroup(self): - """A list of groups that belong to the permission group""" - return self._subgroup - @subgroup.setter - def subgroup(self, value): - self._subgroup = value - api_args = {'subgroup': self._subgroup, - 'group_name': self._group_name} - self._update(api_args) - - @property - def zone(self): - """A list of users that belong to the permission group""" - return self._zone - @zone.setter - def zone(self, value): - self._zone = value - api_args = {'zone': self._zone, - 'group_name': self._group_name} - self._update(api_args) + response = DynectSession.get(self.uri) + self._build(response['data']) - def delete(self): - """Delete this permission group""" - uri = '/PermissionGroup/{}/'.format(self._group_name) - DynectSession.get_session().execute(uri, 'DELETE') + def _update(self, **api_args): + """Update this object on the DynECT System""" + if 'group_name' in api_args: + api_args['new_group_name'] = api_args.pop('group_name') + super(PermissionsGroup, self)._update(#group_name=self.group_name, + **api_args) def add_permission(self, permission): """Adds individual permissions to the user :param permission: the permission to add to this user """ - uri = '/PermissionGroupPermissionEntry/{}/{}/'.format(self._group_name, - permission) - DynectSession.get_session().execute(uri, 'POST') + uri = '/PermissionGroupPermissionEntry/{0}/{1}/'.format( + self._group_name, permission) + DynectSession.post(uri) self._permission.append(permission) def replace_permissions(self, permission=None): - """Replaces a list of individual user permissions for the user + """Replaces a :const:`list` of individual user permissions for the user - :param permission: A list of permissions. Pass an empty list or omit - the argument to clear the list of permissions of the user + :param permission: A :const:`list` of permissions. Pass an empty + :const:`list` or omit the argument to clear the :const:`list` of + permissions of the user """ api_args = {} if permission is not None: api_args['permission'] = permission - uri = '/PermissionGroupPermissionEntry/{}/'.format(self._group_name) - DynectSession.get_session().execute(uri, 'PUT', api_args) + uri = '/PermissionGroupPermissionEntry/{0}/'.format(self._group_name) + DynectSession.put(uri, api_args) if permission: self._permission = permission else: @@ -1026,9 +673,9 @@ def remove_permission(self, permission): :param permission: the permission to remove """ - uri = '/PermissionGroupPermissionEntry/{}/{}/'.format(self._group_name, - permission) - DynectSession.get_session().execute(uri, 'DELETE') + uri = '/PermissionGroupPermissionEntry/{0}/{1}/'.format( + self._group_name, permission) + DynectSession.delete(uri) self._permission.remove(permission) def add_zone(self, zone, recurse='Y'): @@ -1040,8 +687,9 @@ def add_zone(self, zone, recurse='Y'): of a Zone to this :class:`~dyn.tm.accounts.PermissionsGroup` """ api_args = {'recurse': recurse} - uri = '/PermissionGroupZoneEntry/{}/{}/'.format(self._group_name, zone) - DynectSession.get_session().execute(uri, 'POST', api_args) + uri = '/PermissionGroupZoneEntry/{0}/{1}/'.format(self._group_name, + zone) + DynectSession.post(uri, api_args) self._zone.append(zone) def add_subgroup(self, name): @@ -1052,9 +700,9 @@ def add_subgroup(self, name): to be added to this :class:`~dyn.tm.accounts.PermissionsGroup`'s subgroups """ - uri = '/PermissionGroupSubgroupEntry/{}/{}/'.format(self._group_name, - name) - DynectSession.get_session().execute(uri, 'POST') + uri = '/PermissionGroupSubgroupEntry/{0}/{1}/'.format(self._group_name, + name) + DynectSession.post(uri) self._subgroup.append(name) def update_subgroup(self, subgroups): @@ -1064,8 +712,8 @@ def update_subgroup(self, subgroups): :param subgroups: The subgroups with updated information """ api_args = {'subgroup': subgroups} - uri = '/PermissionGroupSubgroupEntry/{}/'.format(self._group_name) - DynectSession.get_session().execute(uri, 'PUT', api_args) + uri = '/PermissionGroupSubgroupEntry/{0}/'.format(self._group_name) + DynectSession.put(uri, api_args) self._subgroup = subgroups def delete_subgroup(self, name): @@ -1076,95 +724,33 @@ def delete_subgroup(self, name): to be remoevd from this :class:`~dyn.tm.accounts.PermissionsGroup`'s subgroups """ - uri = '/PermissionGroupSubgroupEntry/{}/{}/'.format(self._group_name, - name) - DynectSession.get_session().execute(uri, 'DELETE') + uri = '/PermissionGroupSubgroupEntry/{0}/{1}/'.format(self._group_name, + name) + DynectSession.delete(uri) self._subgroup.remove(name) def __str__(self): - """Custom str method""" - return force_unicode(': {}').format(self.group_name) - __repr__ = __unicode__ = __str__ - - def __bytes__(self): - """bytes override""" - return bytes(self.__str__()) + return force_unicode(': {0}').format(self.group_name) -class UserZone(object): - """A DynECT system UserZoneEntry""" - def __init__(self, user_name, zone_name, recurse='Y'): - super(UserZone, self).__init__() - self._user_name = user_name - self._zone_name = zone_name - self._recurse = recurse - api_args = {'recurse': self._recurse} - uri = '/UserZoneEntry/{}/{}/'.format(self._user_name, self._zone_name) - respnose = DynectSession.get_session().execute(uri, 'POST', api_args) - for key, val in respnose['data'].items(): - setattr(self, '_' + key, val) - - @property - def user_name(self): - """User_name property of :class:`~dyn.tm.accounts.UserZone` object is - read only - """ - return self._user_name - @user_name.setter - def user_name(self, value): - pass - - @property - def recurse(self): - """Indicates whether or not permissions should apply to subnodes of - the `zone_name` as well - """ - return self._recurse - @recurse.setter - def recurse(self, value): - self._recurse = value - api_args = {'recurse': self._recurse, 'zone_name': self._zone_name} - uri = '/UserZoneEntry/{}/'.format(self._user_name) - DynectSession.get_session().execute(uri, 'PUT', api_args) - - def update_zones(self, zone=None): - """Replacement list zones where the user will now have permissions. - Pass an empty list or omit the argument to clear the user's zone - permissions - - :param zone: a list of zone names where the user will now have - permissions - """ - if zone is None: - zone = [] - api_args = {'zone': []} - for zone_data in zone: - api_args['zone'].append({'zone_name': zone_data}) - uri = '/UserZoneEntry/{}/'.format(self._user_name) - respnose = DynectSession.get_session().execute(uri, 'PUT', api_args) - for key, val in respnose['data'].items(): - setattr(self, '_' + key, val) +# noinspection PyUnresolvedReferences,PyMissingConstructor +class Notifier(DNSAPIObject): + """DynECT System Notifier""" + #: The DynECT Notifier URI + uri = '/Notifier/' - def delete(self): - """Delete this :class:`~dyn.tm.accounts.UserZone` object from the - DynECT System - """ - api_args = {'recurse': self.recurse} - uri = '/UserZoneEntry/{}/{}/'.format(self._user_name, self._zone_name) - DynectSession.get_session().execute(uri, 'DELETE', api_args) + #: The unique DynECT system id for this Notifier + notifier_id = ImmutableAttribute('notifier_id') - def __str__(self): - """Custom str method""" - return force_unicode(': {}').format(self.user_name) - __repr__ = __unicode__ = __str__ + #: A unique label for this Notifier + label = StringAttribute('label') - def __bytes__(self): - """bytes override""" - return bytes(self.__str__()) + #: A :const:`list` of recipients attached to this Notifier + recipients = ListAttribute('recipients') + #: A :const:`list` of services that this Notifier is attached to + services = ListAttribute('services') -class Notifier(object): - """DynECT System Notifier""" def __init__(self, *args, **kwargs): """Create a new :class:`~dyn.tm.accounts.Notifier` object @@ -1177,20 +763,16 @@ def __init__(self, *args, **kwargs): :param notifier_id: The system id of this :class:`~dyn.tm.accounts.Notifier` """ - super(Notifier, self).__init__() - self._label = self._recipients = self._services = None - self._notifier_id = self.uri = None if 'api' in kwargs: del kwargs['api'] - for key, val in kwargs.items(): - setattr(self, '_' + key, val) - self.uri = '/Notifier/{}/'.format(self._notifier_id) + self._build(kwargs) elif len(args) + len(kwargs) > 1: self._post(*args, **kwargs) elif len(kwargs) > 0 or 'label' in kwargs: self._post(**kwargs) else: self._get(*args, **kwargs) + self.uri = '/Notifier/{}/'.format(self._notifier_id) def _post(self, label=None, recipients=None, services=None): """Create a new :class:`~dyn.tm.accounts.Notifier` object on the @@ -1202,89 +784,74 @@ def _post(self, label=None, recipients=None, services=None): self._label = label self._recipients = recipients self._services = services - response = DynectSession.get_session().execute(uri, 'POST', self) + response = DynectSession.post(uri, self) self._build(response['data']) - self.uri = '/Notifier/{}/'.format(self._notifier_id) def _get(self, notifier_id): """Get an existing :class:`~dyn.tm.accounts.Notifier` object from the DynECT System """ - self._notifier_id = notifier_id - self.uri = '/Notifier/{}/'.format(self._notifier_id) - response = DynectSession.get_session().execute(self.uri, 'GET') + self.uri = '/Notifier/{0}/'.format(notifier_id) + response = DynectSession.get(self.uri) self._build(response['data']) - def _build(self, data): - for key, val in data.items(): - setattr(self, '_' + key, val) + def __str__(self): + return force_unicode(': {0}').format(self.label) - def _update(self, api_args=None): - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) - @property - def notifier_id(self): - """The unique System id for this Notifier""" - return self._notifier_id - @notifier_id.setter - def notifier_id(self, value): - pass - - @property - def label(self): - """The label used to identify this :class:`~dyn.tm.accounts.Notifier` - """ - return self._label - @label.setter - def label(self, value): - self._label = value - api_args = {'label': self._label} - self._update(api_args) - - @property - def recipients(self): - """List of Recipients attached to this - :class:`~dyn.tm.accounts.Notifier` - """ - return self._recipients - @recipients.setter - def recipients(self, value): - self._recipients = value - api_args = {'recipients': self._recipients} - self._update(api_args) - - @property - def services(self): - """List of services attached to this - :class:`~dyn.tm.accounts.Notifier` - """ - return self._services - @services.setter - def services(self, value): - self._services = value - api_args = {'services': self._services} - self._update(api_args) +class Contact(DNSAPIObject): + """A DynECT System Contact""" + #: The DynECT Contact URI + uri = '/Contact/{nickname}/' - def delete(self): - """Delete this :class:`~dyn.tm.accounts.Notifier` from the Dynect - System - """ - DynectSession.get_session().execute(self.uri, 'DELETE') + #: The nickname for this Contact + nickname = StringAttribute('nickname') - def __str__(self): - """Custom str method""" - return force_unicode(': {}').format(self.label) - __repr__ = __unicode__ = __str__ + #: The primary email address associated with this Contact + email = StringAttribute('email') - def __bytes__(self): - """bytes override""" - return bytes(self.__str__()) + #: The first name of this Contact + first_name = StringAttribute('first_name') + #: The last name of this Contact + last_name = StringAttribute('last_name') + + #: The organization that this Contact belongs to + organization = StringAttribute('organization') + + #: The primary phone number for this Contact + phone = StringAttribute('phone') + + #: The primary address associated with this Contact + address = StringAttribute('address') + + #: The address associated with this Contact, line 2 + address_2 = StringAttribute('address_2') + + #: The city that this Contact is from + city = StringAttribute('city') + + #: The country that this Contact is from + country = StringAttribute('country') + + #: The primary fax number for this Contact + fax = StringAttribute('fax') + + #: The primary notification email address for this Contact + notify_email = StringAttribute('notify_email') + + #: The primary pager email address for this Contact + pager_email = StringAttribute('pager_email') + + #: The zip code for this Contact + post_code = StringAttribute('post_code') + + #: The state that this Contact is from + state = StringAttribute('state') + + #: A website associated with this Contact + website = StringAttribute('website') -class Contact(object): - """A DynECT System Contact""" def __init__(self, nickname, *args, **kwargs): """Create a :class:`~dyn.tm.accounts.Contact` object @@ -1318,26 +885,9 @@ def __init__(self, nickname, *args, **kwargs): the :class:`~dyn.tm.accounts.Contact`'s address :param website: The :class:`~dyn.tm.accounts.Contact`'s website """ - super(Contact, self).__init__() + self.uri = self.uri.format(nickname=nickname) self._nickname = nickname - self._email = self._first_name = self._last_name = None - self._organization = self._address = self._address_2 = self._city = None - self._country = self._fax = self._notify_email = None - self._pager_email = self._phone = self._post_code = self._state = None - self._website = None - self.uri = '/Contact/{}/'.format(self._nickname) - if 'api' in kwargs: - del kwargs['api'] - for key, val in kwargs.items(): - if key != '_nickname': - setattr(self, '_' + key, val) - else: - setattr(self, key, val) - self.uri = '/Contact/{}/'.format(self._nickname) - elif len(args) == 0 and len(kwargs) == 0: - self._get() - else: - self._post(*args, **kwargs) + super(Contact, self).__init__(*args, **kwargs) def _post(self, email, first_name, last_name, organization, address=None, address_2=None, city=None, country=None, fax=None, @@ -1360,212 +910,19 @@ def _post(self, email, first_name, last_name, organization, address=None, self._post_code = post_code self._state = state self._website = website - response = DynectSession.get_session().execute(self.uri, 'POST', self) + response = DynectSession.post(self.uri, self) self._build(response['data']) - def _get(self): - """Get an existing :class:`~dyn.tm.accounts.Contact` from the DynECT - System - """ - response = DynectSession.get_session().execute(self.uri, 'GET') - for key, val in response['data'].items(): - setattr(self, '_' + key, val) - def _build(self, data): - for key, val in data.items(): - setattr(self, '_' + key, val) - - def _update(self, api_args=None): - """Private update method which handles building this - :class:`~dyn.tm.accounts.Contact` object from the API JSON respnose - """ - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) + if '_nickname' in data: + setattr(self, '_nickname', data.pop('_nickname')) + super(Contact, self)._build(data) + self.uri = '/Contact/{0}/'.format(self._nickname) - @property - def nickname(self): - """This :class:`~dyn.tm.accounts.Contact`'s DynECT System Nickname""" - return self._nickname - @nickname.setter - def nickname(self, value): - self._nickname = value - api_args = {'new_nickname': self._nickname} - self._update(api_args) - - @property - def email(self): - """This :class:`~dyn.tm.accounts.Contact`'s DynECT System Email address - """ - return self._email - @email.setter - def email(self, value): - self._email = value - api_args = {'email': self._email} - self._update(api_args) - - @property - def first_name(self): - """The first name of this :class:`~dyn.tm.accounts.Contact`""" - return self._first_name - @first_name.setter - def first_name(self, value): - self._first_name = value - api_args = {'first_name': self._first_name} - self._update(api_args) - - @property - def last_name(self): - """The last name of this :class:`~dyn.tm.accounts.Contact`""" - return self._last_name - @last_name.setter - def last_name(self, value): - self._last_name = value - api_args = {'last_name': self._last_name} - self._update(api_args) - - @property - def organization(self): - """The organization this :class:`~dyn.tm.accounts.Contact` belongs to - within the DynECT System - """ - return self._organization - @organization.setter - def organization(self, value): - self._organization = value - api_args = {'organization': self._organization} - self._update(api_args) - - @property - def phone(self): - """The phone number associated with this - :class:`~dyn.tm.accounts.Contact` - """ - return self._phone - @phone.setter - def phone(self, value): - self._phone = value - api_args = {'phone': self._phone} - self._update(api_args) - - @property - def address(self): - """This :class:`~dyn.tm.accounts.Contact`'s street address""" - return self._address - @address.setter - def address(self, value): - self._address = value - api_args = {'address': self._address} - self._update(api_args) - - @property - def address_2(self): - """This :class:`~dyn.tm.accounts.Contact`'s street address, line 2""" - return self._address_2 - @address_2.setter - def address_2(self, value): - self._address_2 = value - api_args = {'address_2': self._address_2} - self._update(api_args) - - @property - def city(self): - """This :class:`~dyn.tm.accounts.Contact`'s city""" - return self._city - @city.setter - def city(self, value): - self._city = value - api_args = {'city': self._city} - self._update(api_args) - - @property - def country(self): - """This :class:`~dyn.tm.accounts.Contact`'s Country""" - return self._country - @country.setter - def country(self, value): - self._country = value - api_args = {'country': self._country} - self._update(api_args) - - @property - def fax(self): - """The fax number associated with this - :class:`~dyn.tm.accounts.Contact` - """ - return self._fax - @fax.setter - def fax(self, value): - self._fax = value - api_args = {'fax': self._fax} - self._update(api_args) - - @property - def notify_email(self): - """Email address where this :class:`~dyn.tm.accounts.Contact` should - receive notifications - """ - return self._notify_email - @notify_email.setter - def notify_email(self, value): - self._notify_email = value - api_args = {'notify_email': self._notify_email} - self._update(api_args) - - @property - def pager_email(self): - """Email address where this :class:`~dyn.tm.accounts.Contact` should - receive messages destined for a pager - """ - return self._pager_email - @pager_email.setter - def pager_email(self, value): - self._pager_email = value - api_args = {'pager_email': self._pager_email} - self._update(api_args) - - @property - def post_code(self): - """This :class:`~dyn.tm.accounts.Contacts`'s postal code, part of the - contacts's address - """ - return self._post_code - @post_code.setter - def post_code(self, value): - self._post_code = value - api_args = {'post_code': self._post_code} - self._update(api_args) - - @property - def state(self): - """This :class:`~dyn.tm.accounts.Contact`'s state""" - return self._state - @state.setter - def state(self, value): - self._state = value - api_args = {'state': self._state} - self._update(api_args) - - @property - def website(self): - """This :class:`~dyn.tm.accounts.Contact`'s website""" - return self._website - @website.setter - def website(self, value): - self._website = value - api_args = {'website': self._website} - self._update(api_args) - - def delete(self): - """Delete this :class:`~dyn.tm.accounts.Contact` from the Dynect System - """ - DynectSession.get_session().execute(self.uri, 'DELETE') + def _update(self, **api_args): + if 'nickname' in api_args: + api_args['new_nickname'] = api_args.pop('nickname') + super(Contact, self)._update(**api_args) def __str__(self): - """Custom str method""" - return force_unicode(': {}').format(self.nickname) - __repr__ = __unicode__ = __str__ - - def __bytes__(self): - """bytes override""" - return bytes(self.__str__()) + return force_unicode(': {0}').format(self.nickname) diff --git a/dyn/tm/errors.py b/dyn/tm/errors.py index fb8406d..eedaa68 100644 --- a/dyn/tm/errors.py +++ b/dyn/tm/errors.py @@ -4,9 +4,9 @@ completely unexpected happens TODO: add a DynectInvalidPermissionsError """ -__all__ = ['DynectAuthError', 'DynectInvalidArgumentError', 'DynectCreateError', - 'DynectUpdateError', 'DynectGetError', 'DynectDeleteError', - 'DynectQueryTimeout'] +__all__ = ['DynectAuthError', 'DynectInvalidArgumentError', + 'DynectCreateError', 'DynectUpdateError', 'DynectGetError', + 'DynectDeleteError', 'DynectQueryTimeout'] __author__ = 'jnappi' diff --git a/dyn/tm/records.py b/dyn/tm/records.py index 115cef2..3df55e8 100644 --- a/dyn/tm/records.py +++ b/dyn/tm/records.py @@ -4,8 +4,9 @@ These DNS_Records should really only need to be created via a zone instance but could also be created independently if passed valid zone, fqdn data """ -from .errors import DynectInvalidArgumentError -from .session import DynectSession +from .session import DynectSession, DNSAPIObject +from ..core import (ImmutableAttribute, IntegerAttribute, StringAttribute, + ValidatedAttribute) from ..compat import force_unicode __author__ = 'jnappi' @@ -14,77 +15,79 @@ 'KEYRecord', 'KXRecord', 'LOCRecord', 'IPSECKEYRecord', 'MXRecord', 'NAPTRRecord', 'PTRRecord', 'PXRecord', 'NSAPRecord', 'RPRecord', 'NSRecord', 'SOARecord', 'SPFRecord', 'SRVRecord', 'TLSARecord', - 'TXTRecord'] + 'TXTRecord', 'RECORD_TYPES'] -class DNSRecord(object): +# noinspection PyMissingConstructor +class DNSRecord(DNSAPIObject): """Base record object contains functionality to be used across all other record type objects """ + #: Name of zone that this record belongs to + zone = ImmutableAttribute('zone') - def __init__(self, zone, fqdn, create=True): - super(DNSRecord, self).__init__() - self._zone = zone - self._fqdn = fqdn - self._ttl = None - self._record_type = None - self._record_id = None - self.create = create - self.api_args = {'rdata': {}} - - def _create_record(self, api_args): + #: The fully qualified domain name where this record can be found + fqdn = ImmutableAttribute('fqdn') + + #: The unique DynECT System id for this record. Note, that this id will + #: change when a record is updated. Outdated record id's can still be used + #: to retrieve older versions of records from the DynECT system + record_id = ImmutableAttribute('record_id') + + #: The time to live for this DNS Record + ttl = IntegerAttribute('ttl') + + #: A :const:`str` representation of what type this record is + record_type = '' + + def __init__(self, zone, fqdn, record_id=None, *args, **kwargs): + self._zone, self._fqdn, self._record_id = zone, fqdn, record_id + self.uri = '/{0}/{1}/{2}/'.format(self.record_type, zone, fqdn) + if 'api' in kwargs: + del kwargs['api'] + self._build(kwargs) + elif record_id: + self._get(record_id) + else: + self._post(*args, **kwargs) + self.uri += '{0}/'.format(self.record_id) + + def _post(self, *args, **api_args): """Make the API call to create the current record type :param api_args: arguments to be pased to the API call """ - if self.create: - if not self._fqdn.endswith('.'): - self._fqdn += '.' - if not self._record_type.endswith('Record'): - self._record_type += 'Record' - uri = '/{}/{}/{}/'.format(self._record_type, self._zone, - self._fqdn) - response = DynectSession.get_session().execute(uri, 'POST', - api_args) - self._build(response['data']) - - def _get_record(self, record_id): + response = DynectSession.post(self.uri, api_args) + self._build(response['data']) + + def _get(self, record_id): """Get an existing record object from the DynECT System :param record_id: The id of the record you would like to get """ - if self.create: - if not self._fqdn.endswith('.'): - self._fqdn += '.' - if not self._record_type.endswith('Record'): - self._record_type += 'Record' - self._record_id = record_id - uri = '/{}/{}/{}/{}/'.format(self._record_type, self._zone, - self._fqdn, self._record_id) - response = DynectSession.get_session().execute(uri, 'GET', {}) - self._build(response['data']) - - def _update_record(self, api_args): - """Make the API call to update the current record type + uri = self.uri + '{0}/'.format(record_id) + response = DynectSession.get(uri) + self._build(response['data']) - :param api_args: arguments to be pased to the API call + def _update(self, **api_args): + """Build our records rdata api_argument attribute, and ship that off to + the API """ - if not self._fqdn.endswith('.'): - self._fqdn += '.' - if not self._record_type.endswith('Record'): - self._record_type += 'Record' - uri = '/{}/{}/{}/{}/'.format(self._record_type, self._zone, self._fqdn, - self._record_id) - response = DynectSession.get_session().execute(uri, 'PUT', api_args) - self._build(response['data']) + rdata = DNSRecord.rdata(self) + for key, val in api_args.items(): + if key in rdata: + rdata[key] = val + api_args['rdata'] = rdata + super(DNSRecord, self)._update(**api_args) def _build(self, data): - for key, val in data.items(): - if key == 'rdata': - for r_key, r_val in val.items(): - setattr(self, '_' + r_key, r_val) - else: - setattr(self, '_' + key, val) + """Flatten the inner rdata dict for convenience and ship that data off + to be built + """ + if 'rdata' in data: + for r_key, r_val in data.pop('rdata').items(): + data[r_key] = r_val + super(DNSRecord, self)._build(data) def rdata(self): """Return a records rdata""" @@ -92,7 +95,8 @@ def rdata(self): for key, val in self.__dict__.items(): if key.startswith('_') and not hasattr(val, '__call__') \ and key != '_record_type' and key != '_record_id': - if 'ttl' not in key and 'zone' not in key and 'fqdn' not in key: + if 'ttl' not in key and 'zone' not in key \ + and 'fqdn' not in key: rdata[key[1:]] = val return rdata @@ -107,103 +111,20 @@ def geo_rdata(self): @property def rec_name(self): - return self._record_type.replace('Record', '').lower() - - def delete(self): - """Delete the current record""" - api_args = {} - if not self._fqdn.endswith('.'): - self._fqdn += '.' - if not self._record_type.endswith('Record'): - self._record_type += 'Record' - uri = '/{}/{}/{}/{}/'.format(self._record_type, self.zone, self.fqdn, - self._record_id) - DynectSession.get_session().execute(uri, 'DELETE', api_args) - - @property - def zone(self): - """Once the zone is set, it will be a read only property""" - return self._zone - - @zone.setter - def zone(self, value): - pass - - @property - def fqdn(self): - """Once the fqdn is set, it will be a read only property""" - return self._fqdn - - @fqdn.setter - def fqdn(self, value): - pass - - @property - def record_id(self): - """The unique ID of this record from the DynECT System""" - return self._record_id - - @record_id.setter - def record_id(self, value): - pass - - @property - def ttl(self): - """The TTL for this record""" - return self._ttl - - @ttl.setter - def ttl(self, value): - """Set the value of this record's ttl property""" - self._ttl = value - self.api_args['ttl'] = self._ttl - self._update_record(self.api_args) + """Convenience property to access the name of this record type""" + return self.record_type.replace('Record', '').lower() def __str__(self): - """str override""" - return force_unicode('<{}>: {}').format(self._record_type, self._fqdn) - - __repr__ = __unicode__ = __str__ - - def __bytes__(self): - """bytes override""" - return bytes(self.__str__()) + return force_unicode('<{0}>: {1}').format(self.record_type, self.fqdn) class ARecord(DNSRecord): """The IPv4 Address (A) Record forward maps a host name to an IPv4 address. """ + record_type = 'ARecord' - def __init__(self, zone, fqdn, *args, **kwargs): - """Create an :class:`~dyn.tm.records.ARecord` object - - :param zone: Name of zone where the record will be added - :param fqdn: Name of node where the record will be added - :param address: IPv4 address for the record - :param ttl: TTL for this record - """ - if 'create' in kwargs: - super(ARecord, self).__init__(zone, fqdn, kwargs['create']) - del kwargs['create'] - self._build(kwargs) - self._record_type = 'ARecord' - else: - super(ARecord, self).__init__(zone, fqdn) - self._record_type = 'ARecord' - self._ttl = self._address = None - if 'record_id' in kwargs: - self._get_record(kwargs['record_id']) - else: - self._post(*args, **kwargs) - - def _post(self, address, ttl=0): - """Create a new :class:`~dyn.tm.records.ARecord` on the DynECT System - """ - self._address = address - self._ttl = ttl - self.api_args = {'rdata': {'address': self._address}, - 'ttl': self._ttl} - self._create_record(self.api_args) + #: IPv4 address for the record + address = StringAttribute('address') def rdata(self): """Return this :class:`~dyn.tm.records.ARecord`'s rdata as a JSON blob @@ -212,65 +133,20 @@ def rdata(self): shell = {'a_rdata': guts} return shell - @property - def address(self): - """Return the value of this record's address property""" - return self._address - - @address.setter - def address(self, value): - """Set the value of this record's address property""" - self._address = value - if 'rdata' not in self.api_args: - self.api_args['rdata'] = {} - self.api_args['rdata']['address'] = self._address - self._update_record(self.api_args) - def __str__(self): """str override""" - return ': {}'.format(self._address) - - def __repr__(self): - """print override""" - return ': {}'.format(self._address) + return ': {0}'.format(self.address) + __repr__ = __str__ class AAAARecord(DNSRecord): """The IPv6 Address (AAAA) Record is used to forward map hosts to IPv6 addresses and is the current IETF recommendation for this purpose. """ + record_type = 'AAAARecord' - def __init__(self, zone, fqdn, *args, **kwargs): - """Create an :class:`~dyn.tm.records.AAAARecord` object - - :param zone: Name of zone where the record will be added - :param fqdn: Name of node where the record will be added - :param address: IPv6 address for the record - :param ttl: TTL for this record - """ - if 'create' in kwargs: - super(AAAARecord, self).__init__(zone, fqdn, kwargs['create']) - del kwargs['create'] - self._build(kwargs) - self._record_type = 'AAAARecord' - else: - super(AAAARecord, self).__init__(zone, fqdn) - self._record_type = 'AAAARecord' - self._address = None - if 'record_id' in kwargs: - self._get_record(kwargs['record_id']) - else: - self._post(*args, **kwargs) - - def _post(self, address, ttl=0): - """Create a new :class:`~dyn.tm.records.AAAARecord` on the DynECT - System - """ - self._address = address - self._ttl = ttl - self.api_args = {'rdata': {'address': self._address}, - 'ttl': self._ttl} - self._create_record(self.api_args) + #: IPv6 address for the record + address = StringAttribute('address') def rdata(self): """Return this :class:`~dyn.tm.records.AAAARecord`'s rdata as a JSON @@ -280,76 +156,29 @@ def rdata(self): shell = {'aaaa_rdata': guts} return shell - @property - def address(self): - """Return the value of this record's address property""" - return self._address - - @address.setter - def address(self, value): - """Set the value of this record's address property""" - self._address = value - self.api_args['rdata']['address'] = self._address - self._update_record(self.api_args) - def __str__(self): """str override""" - return ': {}'.format(self._address) - - def __repr__(self): - """print override""" - return ': {}'.format(self._address) + return ': {0}'.format(self.address) + __repr__ = __str__ class CERTRecord(DNSRecord): """The Certificate (CERT) Record may be used to store either public key certificates or Certificate Revocation Lists (CRL) in the zone file. """ + record_type = 'CERTRecord' - def __init__(self, zone, fqdn, *args, **kwargs): - """Create a :class:`~dyn.tm.records.CERTRecord` object - - :param zone: Name of zone where the record will be added - :param fqdn: Name of node where the record will be added - :param format: Numeric value for the certificate type - :param tag: Numeric value for the public key certificate - :param algorithm: Public key algorithm number used to generate the - certificate - :param certificate: The public key certificate - :param ttl: TTL for this record in seconds - """ - if 'create' in kwargs: - super(CERTRecord, self).__init__(zone, fqdn, kwargs['create']) - del kwargs['create'] - self._build(kwargs) - self._record_type = 'CERTRecord' - else: - super(CERTRecord, self).__init__(zone, fqdn) - self._record_type = 'CERTRecord' - self._format = self._tag = self._algorithm = None - self._certificate = None - if 'record_id' in kwargs: - self._get_record(kwargs['record_id']) - elif len(args) + len(kwargs) == 1: - self._get_record(*args, **kwargs) - elif len(args) + len(kwargs) > 1: - self._post(*args, **kwargs) - - def _post(self, format, tag, algorithm, certificate, ttl=0): - """Create a new :class:`~dyn.tm.records.CERTRecord` on the DynECT - System - """ - self._format = format - self._tag = tag - self._algorithm = algorithm - self._certificate = certificate - self._ttl = ttl - self.api_args = {'rdata': {'format': self._format, - 'tag': self._tag, - 'algorithm': self._algorithm, - 'certificate': self._certificate}, - 'ttl': self._ttl} - self._create_record(self.api_args) + #: Numeric value for the certificate type + format = IntegerAttribute('format') + + #: Numeric value for the public key certificate + tag = IntegerAttribute('tag') + + #: Public key algorithm number used to generate the certificate + algorithm = StringAttribute('algorithm') + + #: The public key certificate + certificate = StringAttribute('certificate') def rdata(self): """Return this :class:`~dyn.tm.records.CERTRecord`'s rdata as a JSON @@ -359,87 +188,15 @@ def rdata(self): shell = {'cert_rdata': guts} return shell - @property - def format(self): - """Numeric value for the certificate type.""" - return self._format - - @format.setter - def format(self, value): - self._format = value - self.api_args['rdata']['format'] = self._format - self._update_record(self.api_args) - - @property - def tag(self): - """Numeric value for the public key certificate""" - return self._tag - - @tag.setter - def tag(self, value): - self._tag = value - self.api_args['rdata']['tag'] = self._tag - self._update_record(self.api_args) - - @property - def algorithm(self): - """Public key algorithm number used to generate the certificate""" - return self._algorithm - - @algorithm.setter - def algorithm(self, value): - self._algorithm = value - self.api_args['rdata']['algorithm'] = self._algorithm - self._update_record(self.api_args) - - @property - def certificate(self): - """The public key certificate""" - return self._certificate - - @certificate.setter - def certificate(self, value): - self._certificate = value - self.api_args['rdata']['certificate'] = self._certificate - self._update_record(self.api_args) - class CNAMERecord(DNSRecord): """The Canonical Name (CNAME) Records map an alias to the real or canonical name that may lie inside or outside the current zone. """ + record_type = 'CNAMERecord' - def __init__(self, zone, fqdn, *args, **kwargs): - """Create an :class:`~dyn.tm.records.CNAMERecord` object - - :param zone: Name of zone where the record will be added - :param fqdn: Name of node where the record will be added - :param cname: Hostname - :param ttl: TTL for this record - """ - if 'create' in kwargs: - super(CNAMERecord, self).__init__(zone, fqdn, kwargs['create']) - del kwargs['create'] - self._build(kwargs) - self._record_type = 'CNAMERecord' - else: - super(CNAMERecord, self).__init__(zone, fqdn) - self._record_type = 'CNAMERecord' - self._cname = None - if 'record_id' in kwargs: - self._get_record(kwargs['record_id']) - else: - self._post(*args, **kwargs) - - def _post(self, cname, ttl=0): - """Create a new :class:`~dyn.tm.records.CNAMERecord` on the DynECT - System - """ - self._cname = cname - self._ttl = ttl - self.api_args = {'rdata': {'cname': self._cname}, - 'ttl': self._ttl} - self._create_record(self.api_args) + #: The hostname that this CNAME Record points to + cname = StringAttribute('cname') def rdata(self): """Return this :class:`~dyn.tm.records.CNAMERecord`'s rdata as a JSON @@ -449,25 +206,6 @@ def rdata(self): shell = {'cname_rdata': guts} return shell - @property - def cname(self): - """Hostname""" - return self._cname - - @cname.setter - def cname(self, value): - self._cname = value - self.api_args['rdata']['cname'] = self._cname - self._update_record(self.api_args) - - def __eq__(self, other): - """Equivalence override""" - if isinstance(other, CNAMERecord): - return self.cname == other.cname - elif isinstance(other, str): - return self.cname == other - return False - class DHCIDRecord(DNSRecord): """The :class:`~dyn.tm.records.DHCIDRecord` provides a means by which DHCP @@ -475,42 +213,10 @@ class DHCIDRecord(DNSRecord): so that multiple DHCP clients and servers may deterministically perform dynamic DNS updates to the same zone. """ + record_type = 'DHCIDRecord' - def __init__(self, zone, fqdn, *args, **kwargs): - """Create an :class:`~dyn.tm.records.DHCIDRecord` object - - :param zone: Name of zone where the record will be added - :param fqdn: Name of node where the record will be added - :param digest: Base-64 encoded digest of DHCP data - :param ttl: TTL for this record - """ - if 'create' in kwargs: - super(DHCIDRecord, self).__init__(zone, fqdn, kwargs['create']) - del kwargs['create'] - self._build(kwargs) - self._record_type = 'DHCIDRecord' - else: - super(DHCIDRecord, self).__init__(zone, fqdn) - self._record_type = 'DHCIDRecord' - self._digest = None - if 'record_id' in kwargs: - self._get_record(kwargs['record_id']) - elif len(args) + len(kwargs) == 1: - self._get_record(*args, **kwargs) - elif 'digest' in kwargs or 'ttl' in kwargs: - self._post(*args, **kwargs) - elif len(args) + len(kwargs) == 2: - self._post(*args, **kwargs) - - def _post(self, digest, ttl=0): - """Create a new :class:`~dyn.tm.records.DHCIDRecord` on the DynECT - System - """ - self._digest = digest - self._ttl = ttl - self.api_args = {'rdata': {'digest': self._digest}, - 'ttl': self._ttl} - self._create_record(self.api_args) + #: Base-64 encoded digest of DHCP data + digest = StringAttribute('digest') def rdata(self): """Return this :class:`~dyn.tm.records.DHCIDRecord`'s rdata as a JSON @@ -520,59 +226,18 @@ def rdata(self): shell = {'dhcid_rdata': guts} return shell - @property - def digest(self): - """Base-64 encoded digest of DHCP data""" - return self._digest - - @digest.setter - def digest(self, value): - self._digest = value - self.api_args['rdata']['digest'] = self._digest - self._update_record(self.api_args) - class DNAMERecord(DNSRecord): """The Delegation of Reverse Name (DNAME) Record is designed to assist the delegation of reverse mapping by reducing the size of the data that must be - entered. DNAME's are designed to be used in conjunction with a bit label but - do not strictly require one. However, do note that without a bit label a - DNAME is equivalent to a CNAME when used in a reverse-map zone file. + entered. DNAME's are designed to be used in conjunction with a bit label + but do not strictly require one. However, do note that without a bit label + a DNAME is equivalent to a CNAME when used in a reverse-map zone file. """ + record_type = 'DNAMERecord' - def __init__(self, zone, fqdn, *args, **kwargs): - """Create an :class:`~dyn.tm.records.DNAMERecord` object - - :param zone: Name of zone where the record will be added - :param fqdn: Name of node where the record will be added - :param dname: Target Hostname - :param ttl: TTL for this record - """ - if 'create' in kwargs: - super(DNAMERecord, self).__init__(zone, fqdn, kwargs['create']) - del kwargs['create'] - self._build(kwargs) - self._record_type = 'DNAMERecord' - else: - super(DNAMERecord, self).__init__(zone, fqdn) - self._record_type = 'DNAMERecord' - self._dname = None - if 'record_id' in kwargs: - self._get_record(kwargs['record_id']) - elif 'dname' in kwargs or 'ttl' in kwargs: - self._post(*args, **kwargs) - elif len(args) + len(kwargs) == 2: - self._post(*args, **kwargs) - - def _post(self, dname, ttl=0): - """Create a new :class:`~dyn.tm.records.DNAMERecord` on the DynECT - System - """ - self._dname = dname - self._ttl = ttl - self.api_args = {'rdata': {'dname': self._dname}, - 'ttl': self._ttl} - self._create_record(self.api_args) + #: Target hostname pointed to by this :class:`~dyn.tm.records.DNAMERecord` + dname = StringAttribute('dname') def rdata(self): """Return this :class:`~dyn.tm.records.DNAMERecord`'s rdata as a JSON @@ -582,76 +247,27 @@ def rdata(self): shell = {'dname_rdata': guts} return shell - @property - def dname(self): - """Target hostname""" - return self._dname - - @dname.setter - def dname(self, value): - self._dname = value - self.api_args['rdata']['dname'] = self._dname - self._update_record(self.api_args) - class DNSKEYRecord(DNSRecord): """The DNSKEY Record describes the public key of a public key (asymmetric) cryptographic algorithm used with DNSSEC.nis. It is typically used to authenticate signed keys or zones. """ + record_type = 'DNSKEYRecord' - def __init__(self, zone, fqdn, *args, **kwargs): - """Create a :class:`~dyn.tm.records.DNSKEYRecord` object - - :param zone: Name of zone where the record will be added - :param fqdn: Name of node where the record will be added - :param protocol: Numeric value for protocol - :param public_key: The public key for the DNSSEC signed zone - :param algorithm: Numeric value representing the public key encryption - algorithm which will sign the zone. Must be one of 1 (RSA-MD5), 2 - (Diffie-Hellman), 3 (DSA/SHA-1), 4 (Elliptic Curve), or - 5 (RSA-SHA-1) - :param flags: Numeric value confirming this is the zone's DNSKEY - :param ttl: TTL for this record. Use 0 for zone default - """ - if 'create' in kwargs: - super(DNSKEYRecord, self).__init__(zone, fqdn, kwargs['create']) - del kwargs['create'] - self._build(kwargs) - self._record_type = 'DNSKEYRecord' - else: - super(DNSKEYRecord, self).__init__(zone, fqdn) - self._record_type = 'DNSKEYRecord' - self._algorithm = self._flags = self._protocol = None - self._public_key = None - if 'record_id' in kwargs: - self._get_record(kwargs['record_id']) - elif len(args) + len(kwargs) == 1: - self._get_record(*args, **kwargs) - elif 'protocol' in kwargs or 'public_key' in kwargs: - self._post(*args, **kwargs) - elif len(args) + len(kwargs) > 1: - self._post(*args, **kwargs) - - def _post(self, protocol, public_key, algorithm=5, flags=256, - ttl=0): - """Create a new :class:`~dyn.tm.records.DNSKEYRecord` on the DynECT - System - """ - valid = range(1, 6) - if algorithm not in valid: - raise DynectInvalidArgumentError('algorthim', algorithm, valid) - self._algorithm = algorithm - self._flags = flags - self._protocol = protocol - self._public_key = public_key - self._ttl = ttl - self.api_args = {'rdata': {'algorithm': self._algorithm, - 'flags': self._flags, - 'protocol': self._protocol, - 'public_key': self._public_key}, - 'ttl': self._ttl} - self._create_record(self.api_args) + #: Numeric value for protocol + protocol = IntegerAttribute('protocol') + + #: The public key for the DNSSEC signed zone + public_key = StringAttribute('public_key') + + #: Numeric value representing the public key encryption algorithm which + #: will sign the zone. Must be one of 1 (RSA-MD5), 2 (Diffie-Hellman), + #: 3 (DSA/SHA-1), 4 (Elliptic Curve), or 5 (RSA-SHA-1) + algorithm = ValidatedAttribute('algorithm', validator=range(1, 6)) + + #: Numeric value confirming this is the zone's DNSKEY + flags = IntegerAttribute('flags') def rdata(self): """Return this :class:`~dyn.tm.records.DNSKEYRecord`'s rdata as a JSON @@ -661,109 +277,29 @@ def rdata(self): shell = {'dnskey_rdata': guts} return shell - @property - def algorithm(self): - """Public key encryption algorithm will sign the zone""" - return self._algorithm - - @algorithm.setter - def algorithm(self, value): - self._algorithm = value - self.api_args['rdata']['algorithm'] = self._algorithm - self._update_record(self.api_args) - - @property - def flags(self): - """Numeric value confirming this is the zone's DNSKEY""" - return self._flags - - @flags.setter - def flags(self, value): - self._flags = value - self.api_args['rdata']['flags'] = self._flags - self._update_record(self.api_args) - - @property - def protocol(self): - """Numeric value for protocol. Set to 3 for DNSSEC""" - return self._protocol - - @protocol.setter - def protocol(self, value): - self._protocol = value - self.api_args['rdata']['protocol'] = self._protocol - self._update_record(self.api_args) - - @property - def public_key(self): - """The public key for the DNSSEC signed zone""" - return self._public_key - - @public_key.setter - def public_key(self, value): - self._public_key = value - self.api_args['rdata']['public_key'] = self._public_key - self._update_record(self.api_args) - class DSRecord(DNSRecord): """The Delegation Signer (DS) record type is used in DNSSEC to create the chain of trust or authority from a signed parent zone to a signed child zone. """ + record_type = 'DSRecord' - def __init__(self, zone, fqdn, *args, **kwargs): - """Create a :class:`~dyn.tm.records.DSRecord` object - - :param zone: Name of zone where the record will be added - :param fqdn: Name of node where the record will be added - :param digest: The digest in hexadecimal form. 20-byte, - hexadecimal-encoded, one-way hash of the DNSKEY record surrounded - by parenthesis characters '(' & ')' - :param keytag: The digest mechanism to use to verify the digest - :param algorithm: Numeric value representing the public key encryption - algorithm which will sign the zone. Must be one of 1 (RSA-MD5), 2 - (Diffie-Hellman), 3 (DSA/SHA-1), 4 (Elliptic Curve), or - 5 (RSA-SHA-1) - :param digtype: the digest mechanism to use to verify the digest. Valid - values are SHA1, SHA256 - :param ttl: TTL for this record. Use 0 for zone default - """ - if 'create' in kwargs: - super(DSRecord, self).__init__(zone, fqdn, kwargs['create']) - del kwargs['create'] - self._build(kwargs) - self._record_type = 'DSRecord' - else: - super(DSRecord, self).__init__(zone, fqdn) - self._record_type = 'DSRecord' - self._algorithm = self._digest = self._digtype = self._keytag = None - if 'record_id' in kwargs: - self._get_record(kwargs['record_id']) - elif len(args) + len(kwargs) == 1: - self._get_record(*args, **kwargs) - elif 'digest' in kwargs or 'keytag' in kwargs: - self._post(*args, **kwargs) - elif len(args) + len(kwargs) > 1: - self._post(*args, **kwargs) - - def _post(self, digest, keytag, algorithm=5, digtype=1, ttl=0): - """Create a new :class:`~dyn.tm.records.DSRecord` on the DynECT System - """ - self._digest = digest - self._keytag = keytag - valid = range(1, 6) - if algorithm not in valid: - raise DynectInvalidArgumentError('algorthim', algorithm, valid) - self._algorithm = algorithm - self._digtype = digtype - self._ttl = ttl - self.api_args = {'rdata': {'algorithm': self._algorithm, - 'digest': self._digest, - 'digtype': self._digtype, - 'keytag': self._keytag}, - 'ttl': self._ttl} - self._create_record(self.api_args) + #: The digest in hexadecimal form. 20-byte, hexadecimal-encoded, one-way + #: hash of the DNSKEY record surrounded by parenthesis characters '(' & ')' + digest = StringAttribute('digest') + + #: The digest mechanism to use to verify the digest + keytag = IntegerAttribute('keytag') + + #: Numeric value representing the public key encryption algorithm which + #: will sign the zone. Must be one of 1 (RSA-MD5), 2 (Diffie-Hellman), + #: 3 (DSA/SHA-1), 4 (Elliptic Curve), or 5 (RSA-SHA-1) + algorithm = ValidatedAttribute('algorithm', validator=range(1, 6)) + + #: the digest mechanism to use to verify the digest. Valid values are + #: 'SHA1' or 'SHA256' + digtype = ValidatedAttribute('digtype', validator=('SHA1', 'SHA256')) def rdata(self): """Return this :class:`~dyn.tm.records.DSRecord`'s rdata as a JSON blob @@ -772,51 +308,37 @@ def rdata(self): shell = {'ds_rdata': guts} return shell - @property - def algorithm(self): - """Identifies the encoding algorithm""" - return self._algorithm - @algorithm.setter - def algorithm(self, value): - self._algorithm = value - self.api_args['rdata']['algorithm'] = self._algorithm - self._update_record(self.api_args) +class IPSECKEYRecord(DNSRecord): + """The IPSECKEY is used for storage of keys used specifically for IPSec + oerations + """ + record_type = 'IPSECKEYRecord' - @property - def digest(self): - """The digest in hexadecimal form. 20-byte, hexadecimal-encoded, - one-way hash of the DNSKEY record surrounded by parenthesis characters - """ - return self._digest + #: Indicates priority among multiple IPSECKEYS. Lower numbers are higher + #: priority + precedence = IntegerAttribute('precedence') - @digest.setter - def digest(self, value): - self._digest = value - self.api_args['rdata']['digest'] = self._digest - self._update_record(self.api_args) + #: Gateway type. Must be one of 0, 1, 2, or 3 + gatetype = ValidatedAttribute('gatetype', validator=range(0, 4)) - @property - def digtype(self): - """Identifies which digest mechanism to use to verify the digest""" - return self._digtype + #: Public key's cryptographic algorithm and format. Must be one of 0, 1, or + #: 2 + algorithm = ValidatedAttribute('algorithm', validator=range(0, 3)) - @digtype.setter - def digtype(self, value): - self._digtype = value - self.api_args['rdata']['digtype'] = self._digtype - self._update_record(self.api_args) + #: Gateway used to create IPsec tunnel. Based on Gateway type + gateway = IntegerAttribute('gateway') - @property - def keytag(self): - """Identifies which digest mechanism to use to verify the digest""" - return self._keytag + #: Base64 encoding of the public key. Whitespace is allowed + public_key = StringAttribute('public_key') - @keytag.setter - def keytag(self, value): - self._keytag = value - self.api_args['rdata']['keytag'] = self._keytag - self._update_record(self.api_args) + def rdata(self): + """Return this :class:`~dyn.tm.records.IPSECKEYRecord`'s rdata as a + JSON blob + """ + guts = super(IPSECKEYRecord, self).rdata() + shell = {'ipseckey_rdata': guts} + return shell class KEYRecord(DNSRecord): @@ -827,58 +349,21 @@ class KEYRecord(DNSRecord): limited to use in DNS Security operations such as DDNS and zone transfer due to the difficulty of querying for specific uses. """ + record_type = 'KEYRecord' - def __init__(self, zone, fqdn, *args, **kwargs): - """Create a :class:`~dyn.tm.records.KEYRecord` object - - :param zone: Name of zone where the record will be added - :param fqdn: Name of node where the record will be added - :param algorithm: Numeric value representing the public key encryption - algorithm which will sign the zone. Must be one of 1 (RSA-MD5), 2 - (Diffie-Hellman), 3 (DSA/SHA-1), 4 (Elliptic Curve), or - 5 (RSA-SHA-1) - :param flags: See RFC 2535 for information on KEY record flags - :param protocol: Numeric identifier of the protocol for this KEY record - :param public_key: The public key for this record - :param ttl: TTL for the record in seconds - """ - if 'create' in kwargs: - super(KEYRecord, self).__init__(zone, fqdn, kwargs['create']) - del kwargs['create'] - self._build(kwargs) - self._record_type = 'KEYRecord' - else: - super(KEYRecord, self).__init__(zone, fqdn) - self._record_type = 'KEYRecord' - self._algorithm = self._flags = self._protocol = None - self._public_key = None - if 'record_id' in kwargs: - self._get_record(kwargs['record_id']) - elif len(args) + len(kwargs) == 1: - self._get_record(*args, **kwargs) - elif 'algorithm' in kwargs or 'flags' in kwargs or 'protocol' in \ - kwargs or 'public_key' in kwargs or 'ttl' in kwargs: - self._post(*args, **kwargs) - elif len(args) + len(kwargs) > 1: - self._post(*args, **kwargs) - - def _post(self, algorithm, flags, protocol, public_key, ttl=0): - """Create a new :class:`~dyn.tm.records.KEYRecord` on the DynECT System - """ - valid = range(1, 6) - if algorithm not in valid: - raise DynectInvalidArgumentError('algorthim', algorithm, valid) - self._algorithm = algorithm - self._flags = flags - self._protocol = protocol - self._public_key = public_key - self._ttl = ttl - self.api_args = {'rdata': {'algorithm': self._algorithm, - 'flags': self._flags, - 'protocol': self._protocol, - 'public_key': self._public_key}, - 'ttl': self._ttl} - self._create_record(self.api_args) + #: Numeric value representing the public key encryption algorithm which + #: will sign the zone. Must be one of 1 (RSA-MD5), 2 (Diffie-Hellman), + #: 3 (DSA/SHA-1), 4 (Elliptic Curve), or 5 (RSA-SHA-1) + algorithm = ValidatedAttribute('algorithm', validator=range(1, 6)) + + #: See RFC 2535 for information on valid KEY record flags + flags = IntegerAttribute('flags') + + #: Numeric identifier of the protocol for this KEY record + protocol = IntegerAttribute('protocol') + + #: The public key for this record + public_key = StringAttribute('public_key') def rdata(self): """Return this :class:`~dyn.tm.records.KEYRecord`'s rdata as a JSON @@ -888,99 +373,22 @@ def rdata(self): shell = {'key_rdata': guts} return shell - @property - def algorithm(self): - """Numeric identifier for algorithm used""" - return self._algorithm - - @algorithm.setter - def algorithm(self, value): - self._algorithm = value - self.api_args['rdata']['algorithm'] = self._algorithm - self._update_record(self.api_args) - - @property - def flags(self): - """See RFC 2535 for information about Key record flags""" - return self._flags - - @flags.setter - def flags(self, value): - self._flags = value - self.api_args['rdata']['flags'] = self._flags - self._update_record(self.api_args) - - @property - def protocol(self): - """Numeric identifier of the protocol for this KEY record""" - return self._protocol - - @protocol.setter - def protocol(self, value): - self._protocol = value - self.api_args['rdata']['protocol'] = self._protocol - self._update_record(self.api_args) - - @property - def public_key(self): - """The public key for this record""" - return self._public_key - - @public_key.setter - def public_key(self, value): - self._public_key = value - self.api_args['rdata']['public_key'] = self._public_key - self._update_record(self.api_args) - class KXRecord(DNSRecord): """The "Key Exchanger" (KX) Record type is provided with one or more alternative hosts. """ + record_type = 'KXRecord' - def __init__(self, zone, fqdn, *args, **kwargs): - """Create a :class:`~dyn.tm.records.KXRecord` object - - :param zone: Name of zone where the record will be added - :param fqdn: Name of node where the record will be added - :param exchange: Hostname that will act as the Key Exchanger. The - hostname must have a :class:`~dyn.tm.records.CNAMERecord`, an - :class:`~dyn.tm.records.ARecord` and/or an - :class:`~dyn.tm.records.AAAARecord` associated with it - :param preference: Numeric value for priority usage. Lower value takes - precedence over higher value where two records of the same type - exist on the zone/node - :param ttl: TTL for the record in seconds - """ - if 'create' in kwargs: - super(KXRecord, self).__init__(zone, fqdn, kwargs['create']) - del kwargs['create'] - self._build(kwargs) - self._record_type = 'KXRecord' - else: - super(KXRecord, self).__init__(zone, fqdn) - self._record_type = 'KXRecord' - self._exchange = self._preference = None - if 'record_id' in kwargs: - self._get_record(kwargs['record_id']) - elif len(args) + len(kwargs) == 1: - self._get_record(*args, **kwargs) - elif 'exchange' in kwargs or 'preference' in \ - kwargs or 'ttl' in kwargs: - self._post(*args, **kwargs) - elif len(args) + len(kwargs) > 1: - self._post(*args, **kwargs) - - def _post(self, exchange, preference, ttl=0): - """Create a new :class:`~dyn.tm.records.KXRecord` on the DynECT System - """ - self._exchange = exchange - self._preference = preference - self._ttl = ttl - self.api_args = {'rdata': {'exchange': self._exchange, - 'preference': self._preference}, - 'ttl': self._ttl} - self._create_record(self.api_args) + #: Hostname that will act as the Key Exchanger. The hostname must have a + #: :class:`~dyn.tm.records.CNAMERecord`, an + #: :class:`~dyn.tm.records.ARecord` and/or an + #: :class:`~dyn.tm.records.AAAARecord` associated with it + exchange = StringAttribute('exchange') + + #: Numeric value for priority usage. Lower value takes precedence over + #: higher value where two records of the same type exist on the zone/node + preference = IntegerAttribute('preference') def rdata(self): """Return this :class:`~dyn.tm.records.KXRecord`'s rdata as a JSON blob @@ -989,96 +397,28 @@ def rdata(self): shell = {'kx_rdata': guts} return shell - @property - def exchange(self): - """Hostname that will act as the Key Exchanger. The hostname must have - a CNAME record, an A Record and/or an AAAA record associated with it - """ - return self._exchange - - @exchange.setter - def exchange(self, value): - self._exchange = value - self.api_args['rdata']['exchange'] = self._exchange - self._update_record(self.api_args) - - @property - def preference(self): - """Numeric value for priority usage. Lower value takes precedence over - higher value where two records of the same type exist on the zone/node - """ - return self._preference - - @preference.setter - def preference(self, value): - self._preference = value - self.api_args['rdata']['preference'] = self._preference - self._update_record(self.api_args) - class LOCRecord(DNSRecord): """:class:`~dyn.tm.records.LOCRecord`'s allow for the definition of geographic positioning information associated with a host or service name. """ + record_type = 'LOCRecord' - def __init__(self, zone, fqdn, *args, **kwargs): - """Create a :class:`~dyn.tm.records.LOCRecord` object - - :param zone: Name of zone where the record will be added - :param fqdn: Name of node where the record will be added - :param altitude: Measured in meters above sea level - :param horiz_pre: - :param latitude: Measured in degrees, minutes, and seconds with N/S - indicator for North and South - :param longitude: Measured in degrees, minutes, and seconds with E/W - indicator for East and West - :param size: - :param version: - :param vert_pre: - :param ttl: TTL for the record in seconds - """ - if 'create' in kwargs: - super(LOCRecord, self).__init__(zone, fqdn, kwargs['create']) - del kwargs['create'] - self._build(kwargs) - self._record_type = 'LOCRecord' - else: - super(LOCRecord, self).__init__(zone, fqdn) - self._record_type = 'LOCRecord' - self._altitude = self._latitude = self._longitude = None - self._horiz_pre = self._size = self._vert_pre = None - # Version is required to be 0 - self._version = 0 - if 'record_id' in kwargs: - self._get_record(kwargs['record_id']) - elif 'altitude' in kwargs or 'latitude' in \ - kwargs or 'longitude' in kwargs or 'horiz_pre' in \ - kwargs or 'size' in kwargs or 'vert_pre' in \ - kwargs or 'ttl' in kwargs: - self._post(*args, **kwargs) - elif len(args) + len(kwargs) > 1: - self._post(*args, **kwargs) - - def _post(self, altitude, latitude, longitude, horiz_pre=10000, size=1, - vert_pre=10, ttl=0): - """Create a new :class:`~dyn.tm.records.LOCRecord` on the DynECT System - """ - self._altitude = altitude - self._latitude = latitude - self._longitude = longitude - self._horiz_pre = horiz_pre - self._size = size - self._vert_pre = vert_pre - self._ttl = ttl - self.api_args = {'rdata': {'altitude': self._altitude, - 'horiz_pre': self._horiz_pre, - 'latitude': self._latitude, - 'longitude': self._longitude, - 'size': self._size, - 'version': self._version, - 'vert_pre': self._vert_pre}, - 'ttl': self._ttl} - self._create_record(self.api_args) + #: Measured in meters above sea level + altitude = IntegerAttribute('altitude') + + #: Measured in degrees, minutes, and seconds with N/S indicator for North + #: and South + latitude = IntegerAttribute('latitude') + + #: Measured in degrees, minutes, and seconds with E/W indicator for East + #: and West + longitude = IntegerAttribute('longitude') + + horiz_pre = StringAttribute('horiz_pre') + size = IntegerAttribute('size') + version = ValidatedAttribute('version', validator=(0,), default=0) + vert_pre = StringAttribute('vert_pre') def rdata(self): """Return this :class:`~dyn.tm.records.LOCRecord`'s rdata as a JSON @@ -1088,262 +428,20 @@ def rdata(self): shell = {'loc_rdata': guts} return shell - @property - def altitude(self): - """Measured in meters above sea level""" - return self._altitude - - @altitude.setter - def altitude(self, value): - self._altitude = value - self.api_args['rdata']['altitude'] = self._altitude - self._update_record(self.api_args) - - @property - def latitude(self): - """Measured in degrees, minutes, and seconds with N/S indicator for - North and South. Example: 45 24 15 N, where 45 = degrees, 24 = minutes, - 15 = seconds - """ - return self._latitude - - @latitude.setter - def latitude(self, value): - self._latitude = value - self.api_args['rdata']['latitude'] = self._latitude - self._update_record(self.api_args) - - @property - def longitude(self): - """Measured in degrees, minutes, and seconds with E/W indicator for - East and West. Example 89 23 18 W, where 89 = degrees, 23 = minutes, - 18 = seconds - """ - return self._longitude - - @longitude.setter - def longitude(self, value): - self._longitude = value - self.api_args['rdata']['longitude'] = self._longitude - self._update_record(self.api_args) - - @property - def horiz_pre(self): - """Defaults to 10,000 meters""" - return self._horiz_pre - - @horiz_pre.setter - def horiz_pre(self, value): - self._horiz_pre = value - self.api_args['rdata']['horiz_pre'] = self._horiz_pre - self._update_record(self.api_args) - - @property - def size(self): - """Defaults to 1 meter""" - return self._size - - @size.setter - def size(self, value): - self._size = value - self.api_args['rdata']['size'] = self._size - self._update_record(self.api_args) - - @property - def vert_pre(self): - return self._vert_pre - - @vert_pre.setter - def vert_pre(self, value): - self._vert_pre = value - self.api_args['rdata']['vert_pre'] = self._vert_pre - self._update_record(self.api_args) - - @property - def version(self): - """Number of the representation. Must be zero (0) - NOTE: Version has no setter, because it will never not be 0 - """ - return self._version - - -class IPSECKEYRecord(DNSRecord): - """The IPSECKEY is used for storage of keys used specifically for IPSec - oerations - """ - - def __init__(self, zone, fqdn, *args, **kwargs): - """Create an :class:`~dyn.tm.records.IPSECKEYRecord` object - - :param zone: Name of zone where the record will be added - :param fqdn: Name of node where the record will be added - :param precedence: Indicates priority among multiple IPSECKEYS. Lower - numbers are higher priority - :param gatetype: Gateway type. Must be one of 0, 1, 2, or 3 - :param algorithm: Public key's cryptographic algorithm and format. Must - be one of 0, 1, or 2 - :param gateway: Gateway used to create IPsec tunnel. Based on Gateway - type - :param public_key: Base64 encoding of the public key. Whitespace is - allowed - :param ttl: TTL for the record in seconds - """ - if 'create' in kwargs: - super(IPSECKEYRecord, self).__init__(zone, fqdn, kwargs['create']) - del kwargs['create'] - self._build(kwargs) - self._record_type = 'IPSECKEYRecord' - else: - super(IPSECKEYRecord, self).__init__(zone, fqdn) - self.valid_gatetypes = range(0, 4) - self.valid_algorithms = range(0, 3) - self._record_type = 'IPSECKEYRecord' - self._precedence = self._gatetype = self._algorithm = None - self._gateway = self._public_key = None - if 'record_id' in kwargs: - self._get_record(kwargs['record_id']) - elif 'precedence' in kwargs or 'gatetype' in \ - kwargs or 'algorithm' in kwargs or 'gateway' in \ - kwargs or 'public_key' in kwargs or 'ttl' in kwargs: - self._post(*args, **kwargs) - elif len(args) + len(kwargs) > 1: - self._post(*args, **kwargs) - - def _post(self, precedence, gatetype, algorithm, gateway, public_key, - ttl=0): - """Create a new :class:`~dyn.tm.records.IPSECKEYRecord` on the DynECT - System - """ - self._precedence = precedence - if gatetype not in self.valid_gatetypes: - raise DynectInvalidArgumentError('gatetype', gatetype, - self.valid_gatetypes) - self._gatetype = gatetype - if algorithm not in self.valid_algorithms: - raise DynectInvalidArgumentError('algorithm', algorithm, - self.valid_algorithms) - self._algorithm = algorithm - self._gateway = gateway - self._public_key = public_key - self._ttl = ttl - self.api_args = {'rdata': {'precedence': self._precedence, - 'gatetype': self._gatetype, - 'algorithm': self._algorithm, - 'gateway': self._gateway, - 'public_key': self._public_key}, - 'ttl': self._ttl} - self._create_record(self.api_args) - - def rdata(self): - """Return this :class:`~dyn.tm.records.IPSECKEYRecord`'s rdata as a - JSON blob - """ - guts = super(IPSECKEYRecord, self).rdata() - shell = {'ipseckey_rdata': guts} - return shell - - @property - def precedence(self): - """Indicates priority among multiple IPSECKEYS. Lower numbers are - higher priority - """ - return self._precedence - - @precedence.setter - def precedence(self, value): - self._precedence = value - self.api_args['rdata']['precedence'] = self._precedence - self._update_record(self.api_args) - - @property - def gatetype(self): - """Gateway type. Must be one of 0, 1, 2, or 3""" - return self._gatetype - - @gatetype.setter - def gatetype(self, value): - self._gatetype = value - self.api_args['rdata']['gatetype'] = self._gatetype - self._update_record(self.api_args) - - @property - def algorithm(self): - """Public key's cryptographic algorithm and format""" - return self._algorithm - - @algorithm.setter - def algorithm(self, value): - self._algorithm = value - self.api_args['rdata']['algorithm'] = self._algorithm - self._update_record(self.api_args) - - @property - def gateway(self): - """Gateway used to create IPsec tunnel. Based on Gateway type""" - return self._gateway - - @gateway.setter - def gateway(self, value): - self._gateway = value - self.api_args['rdata']['gateway'] = self._gateway - self._update_record(self.api_args) - - @property - def public_key(self): - """Base64 encoding of the public key. Whitespace is allowed""" - return self._public_key - - @public_key.setter - def public_key(self, value): - self._public_key = value - self.api_args['rdata']['public_key'] = self._public_key - self._update_record(self.api_args) - class MXRecord(DNSRecord): """The "Mail Exchanger" record type specifies the name and relative preference of mail servers for a Zone. Defined in RFC 1035 """ + record_type = 'MXRecord' - def __init__(self, zone, fqdn, *args, **kwargs): - """Create an :class:`~dyn.tm.records.MXRecord` object - - :param zone: Name of zone where the record will be added - :param fqdn: Name of node where the record will be added - :param exchange: Hostname of the server responsible for accepting mail - messages in the zone - :param preference: Numeric value for priority usage. Lower value takes - precedence over higher value where two records of the same type - exist on the zone/node. - :param ttl: TTL for the record in seconds - """ - if 'create' in kwargs: - super(MXRecord, self).__init__(zone, fqdn, kwargs['create']) - del kwargs['create'] - self._build(kwargs) - self._record_type = 'MXRecord' - else: - super(MXRecord, self).__init__(zone, fqdn) - self._record_type = 'MXRecord' - self._exchange = self._preference = None - if 'record_id' in kwargs: - self._get_record(kwargs['record_id']) - elif 'exchange' in kwargs or 'preference' in \ - kwargs or 'ttl' in kwargs: - self._post(*args, **kwargs) - elif len(args) + len(kwargs) >= 1: - self._post(*args, **kwargs) - - def _post(self, exchange, preference=10, ttl=0): - """Create a new :class:`~dyn.tm.records.MXRecord` on the DynECT System - """ - self._exchange = exchange - self._preference = preference - self._ttl = ttl - self.api_args = {'rdata': {'exchange': self._exchange, - 'preference': self._preference}, - 'ttl': self._ttl} - self._create_record(self.api_args) + #: Hostname of the server responsible for accepting mail messages in the + #: zone + exchange = StringAttribute('exchange') + + #: Numeric value for priority usage. Lower value takes precedence over + #: higher value where two records of the same type exist on the zone/node. + preference = IntegerAttribute('preference') def rdata(self): """Return this :class:`~dyn.tm.records.MXRecord`'s rdata as a JSON blob @@ -1352,97 +450,37 @@ def rdata(self): shell = {'mx_rdata': guts} return shell - @property - def exchange(self): - """Hostname of the server responsible for accepting mail messages in - the zone - """ - return self._exchange - @exchange.setter - def exchange(self, value): - self._exchange = value - self.api_args['rdata']['exchange'] = self._exchange - self._update_record(self.api_args) +class NAPTRRecord(DNSRecord): + """Naming Authority Pointer Records are a part of the Dynamic Delegation + Discovery System (DDDS). The NAPTR is a generic record that defines a + `rule` that may be applied to private data owned by a client application. + """ + record_type = 'NAPTRRecord' - @property - def preference(self): - """Numeric value for priority usage. Lower value takes precedence over - higher value where two records of the same type exist on the zone/node - """ - return self._preference + #: Indicates the required priority for processing NAPTR records. Lowest + #: value is used first. + order = IntegerAttribute('order') - @preference.setter - def preference(self, value): - self._preference = value - self.api_args['rdata']['preference'] = self._preference - self._update_record(self.api_args) + #: Indicates priority where two or more NAPTR records have identical order + #: values. Lowest value is used first. + preference = IntegerAttribute('preference') + #: Always starts with 'e2u+' (E.164 to URI). After the 'e2u+' there is a + #: string that defines the type and optionally the subtype of the URI where + #: this :class:`~dyn.tm.records.NAPTRRecord` points. + services = StringAttribute('services') -class NAPTRRecord(DNSRecord): - """Naming Authority Pointer Records are a part of the Dynamic Delegation - Discovery System (DDDS). The NAPTR is a generic record that defines a `rule` - that may be applied to private data owned by a client application.""" - - def __init__(self, zone, fqdn, *args, **kwargs): - """Create an :class:`~dyn.tm.records.NAPTRRecord` object - - :param zone: Name of zone where the record will be added - :param fqdn: Name of node where the record will be added - :param order: Indicates the required priority for processing NAPTR - records. Lowest value is used first. - :param preference: Indicates priority where two or more NAPTR records - have identical order values. Lowest value is used first. - :param services: Always starts with "e2u+" (E.164 to URI). After the - e2u+ there is a string that defines the type and optionally the - subtype of the URI where this :class:`~dyn.tm.records.NAPTRRecord` - points. - :param regexp: The NAPTR record accepts regular expressions - :param replacement: The next domain name to find. Only applies if this - :class:`~dyn.tm.records.NAPTRRecord` is non-terminal. - :param flags: Should be the letter "U". This indicates that this NAPTR - record terminal - :param ttl: TTL for the record in seconds - """ - if 'create' in kwargs: - super(NAPTRRecord, self).__init__(zone, fqdn, kwargs['create']) - del kwargs['create'] - self._build(kwargs) - self._record_type = 'NAPTRRecord' - else: - super(NAPTRRecord, self).__init__(zone, fqdn) - self._record_type = 'NAPTRRecord' - self._order = self._preference = self._flags = self._services = None - self._regexp = self._replacement = None - if 'record_id' in kwargs: - self._get_record(kwargs['record_id']) - elif 'order' in kwargs or 'preference' in kwargs or 'services' in \ - kwargs or 'regexp' in kwargs or 'replacement' in \ - kwargs or 'flags' in kwargs or 'ttl' in kwargs: - self._post(*args, **kwargs) - elif len(args) + len(kwargs) > 1: - self._post(*args, **kwargs) - - def _post(self, order, preference, services, regexp, replacement, - flags='U', ttl=0): - """Create a new :class:`~dyn.tm.records.NAPTRRecord` on the DynECT - System - """ - self._order = order - self._preference = preference - self._flags = flags - self._services = services - self._regexp = regexp - self._replacement = replacement - self._ttl = ttl - self.api_args = {'rdata': {'order': self._order, - 'preference': self._preference, - 'flags': self._flags, - 'services': self._services, - 'regexp': self._regexp, - 'replacement': self._replacement}, - 'ttl': self._ttl} - self._create_record(self.api_args) + #: The NAPTR record accepts regular expressions + regexp = StringAttribute('regexp') + + #: The next domain name to find. Only applies if this + #: :class:`~dyn.tm.records.NAPTRRecord` is non-terminal. + replacement = StringAttribute('replacement') + + #: Should be the letter 'U'. This indicates that this is a terminal NAPTR + #: record + flags = StringAttribute('flags', default='U') def rdata(self): """Return this :class:`~dyn.tm.records.NAPTRRecord`'s rdata as a JSON @@ -1452,121 +490,54 @@ def rdata(self): shell = {'naptr_rdata': guts} return shell - @property - def order(self): - """Indicates the required priority for processing NAPTR records. Lowest - value is used first - """ - return self._order - - @order.setter - def order(self, value): - self._order = value - self.api_args['rdata']['order'] = self._order - self._update_record(self.api_args) - @property - def preference(self): - """Indicates priority where two or more NAPTR records have identical - order values. Lowest value is used first. - """ - return self._preference +class NSAPRecord(DNSRecord): + """The Network Services Access Point record is the equivalent of an RR for + ISO's Open Systems Interconnect (OSI) system in that it maps a host name to + an endpoint address. + """ + record_type = 'NSAPRecord' - @preference.setter - def preference(self, value): - self._preference = value - self.api_args['rdata']['preference'] = self._preference - self._update_record(self.api_args) + #: Hex-encoded NSAP identifier + nsap = StringAttribute('nsap') - @property - def flags(self): - """Should be the letter "U". This indicates that this NAPTR record - terminal (E.164 number that maps directly to a URI) + def rdata(self): + """Return this :class:`~dyn.tm.records.NSAPRecord`'s rdata as a JSON + blob """ - return self._flags - - @flags.setter - def flags(self, value): - self._flags = value - self.api_args['rdata']['flags'] = self._flags - self._update_record(self.api_args) + guts = super(NSAPRecord, self).rdata() + shell = {'nsap_rdata': guts} + return shell - @property - def services(self): - """Always starts with "e2u+" (E.164 to URI). After the e2u+ there is a - string that defines the type and optionally the subtype of the URI - where this NAPTR record points - """ - return self._services - @services.setter - def services(self, value): - self._services = value - self.api_args['rdata']['services'] = self._services - self._update_record(self.api_args) +class NSRecord(DNSRecord): + """Nameserver Records are used to list all the nameservers that will + respond authoritatively for a domain. + """ + record_type = 'NSRecord' - @property - def regexp(self): - """The NAPTR record accepts regular expressions""" - return self._regexp + #: Hostname of the authoritative Nameserver for the zone + nsdname = StringAttribute('nsdname') - @regexp.setter - def regexp(self, value): - self._regexp = value - self.api_args['rdata']['regexp'] = self._regexp - self._update_record(self.api_args) + #: Hostname of the authoritative Nameserver for the zone + service_class = StringAttribute('service_class') - @property - def replacement(self): - """The next domain name to find. Only applies if this NAPTR record is - non-terminal + def rdata(self): + """Return this :class:`~dyn.tm.records.NSRecord`'s rdata as a JSON blob """ - return self._replacement - - @replacement.setter - def replacement(self, value): - self._replacement = value - self.api_args['rdata']['replacement'] = self._replacement - self._update_record(self.api_args) + guts = super(NSRecord, self).rdata() + shell = {'ns_rdata': guts} + return shell class PTRRecord(DNSRecord): """Pointer Records are used to reverse map an IPv4 or IPv6 IP address to a host name """ + record_type = 'PTRRecord' - def __init__(self, zone, fqdn, *args, **kwargs): - """Create a :class:`~dyn.tm.records.PTRRecord` object - - :param zone: Name of zone where the record will be added - :param fqdn: Name of node where the record will be added - :param ptrdname: The hostname where the IP address should be directed - :param ttl: TTL for the record in seconds - """ - if 'create' in kwargs: - super(PTRRecord, self).__init__(zone, fqdn, kwargs['create']) - del kwargs['create'] - self._build(kwargs) - self._record_type = 'PTRRecord' - else: - super(PTRRecord, self).__init__(zone, fqdn) - self._record_type = 'PTRRecord' - self._ptrdname = None - if 'record_id' in kwargs: - self._get_record(kwargs['record_id']) - elif 'ptrdname' in kwargs or 'ttl' in kwargs: - self._post(*args, **kwargs) - elif len(args) + len(kwargs) > 1: - self._post(*args, **kwargs) - - def _post(self, ptrdname, ttl=0): - """Create a new :class:`~dyn.tm.records.PTRRecord` on the DynECT System - """ - self._ptrdname = ptrdname - self._ttl = ttl - self.api_args = {'rdata': {'ptrdname': self._ptrdname}, - 'ttl': self._ttl} - self._create_record(self.api_args) + #: The hostname where the IP address should be directed + ptrdname = StringAttribute('ptrdname') def rdata(self): """Return this :class:`~dyn.tm.records.PTRRecord`'s rdata as a JSON @@ -1576,64 +547,23 @@ def rdata(self): shell = {'ptr_rdata': guts} return shell - @property - def ptrdname(self): - """Hostname where the IP address should be directed""" - return self._ptrdname - - @ptrdname.setter - def ptrdname(self, value): - self._ptrdname = value - self.api_args['rdata']['ptrdname'] = self._ptrdname - self._update_record(self.api_args) - class PXRecord(DNSRecord): """The X.400 to RFC 822 E-mail RR allows mapping of ITU X.400 format e-mail addresses to RFC 822 format e-mail addresses using a MIXER-conformant gateway. """ + record_type = 'PXRecord' - def __init__(self, zone, fqdn, *args, **kwargs): - """Create an :class:`~dyn.tm.records.PXRecord` object + #: Sets priority for processing records of the same type. Lowest value is + #: processed first. + preference = StringAttribute('preference') - :param zone: Name of zone where the record will be added - :param fqdn: Name of node where the record will be added - :param preference: Sets priority for processing records of the same - type. Lowest value is processed first. - :param map822: mail hostname - :param mapx400: The domain name derived from the X.400 part of MCGAM - :param ttl: TTL for the record in seconds - """ - if 'create' in kwargs: - super(PXRecord, self).__init__(zone, fqdn, kwargs['create']) - del kwargs['create'] - self._build(kwargs) - self._record_type = 'PXRecord' - else: - super(PXRecord, self).__init__(zone, fqdn) - self._record_type = 'PXRecord' - self._preference = self._map822 = self._mapx400 = None - if 'record_id' in kwargs: - self._get_record(kwargs['record_id']) - elif 'preference' in kwargs or 'map822' in kwargs or 'mapx400' in \ - kwargs or 'ttl' in kwargs: - self._post(*args, **kwargs) - elif len(args) + len(kwargs) > 1: - self._post(*args, **kwargs) - - def _post(self, preference, map822, mapx400, ttl=0): - """Create a new :class:`~dyn.tm.records.PXRecord` on the DynECT System - """ - self._preference = preference - self._map822 = map822 - self._mapx400 = mapx400 - self._ttl = ttl - self.api_args = {'rdata': {'preference': self._preference, - 'map822': self._map822, - 'mapx400': self._mapx400}, - 'ttl': self._ttl} - self._create_record(self.api_args) + #: mail hostname + map822 = StringAttribute('map822') + + #: The domain name derived from the X.400 part of MCGAM + mapx400 = StringAttribute('mapx400') def rdata(self): """Return this :class:`~dyn.tm.records.PXRRecord`'s rdata as a JSON @@ -1643,101 +573,6 @@ def rdata(self): shell = {'pxr_rdata': guts} return shell - @property - def preference(self): - """Sets priority for processing records of the same type. Lowest value - is processed first - """ - return self._preference - - @preference.setter - def preference(self, value): - self._preference = value - self.api_args['rdata']['preference'] = self._preference - self._update_record(self.api_args) - - @property - def map822(self): - """mail hostname""" - return self._map822 - - @map822.setter - def map822(self, value): - self._map822 = value - self.api_args['rdata']['map822'] = self._map822 - self._update_record(self.api_args) - - @property - def mapx400(self): - """Enter the domain name derived from the X.400 part of MCGAM""" - return self._mapx400 - - @mapx400.setter - def mapx400(self, value): - self._mapx400 = value - self.api_args['rdata']['mapx400'] = self._mapx400 - self._update_record(self.api_args) - - -class NSAPRecord(DNSRecord): - """The Network Services Access Point record is the equivalent of an RR for - ISO's Open Systems Interconnect (OSI) system in that it maps a host name to - an endpoint address. - """ - - def __init__(self, zone, fqdn, *args, **kwargs): - """Create an :class:`~dyn.tm.records.PXRecord` object - - :param zone: Name of zone where the record will be added - :param fqdn: Name of node where the record will be added - :param nsap: Hex-encoded NSAP identifier - :param ttl: TTL for the record in seconds - """ - if 'create' in kwargs: - super(NSAPRecord, self).__init__(zone, fqdn, kwargs['create']) - del kwargs['create'] - self._build(kwargs) - self._record_type = 'NSAPRecord' - else: - super(NSAPRecord, self).__init__(zone, fqdn) - self._record_type = 'NSAPRecord' - self._nsap = None - if 'record_id' in kwargs: - self._get_record(kwargs['record_id']) - elif 'nsap' in kwargs or 'ttl' in kwargs: - self._post(*args, **kwargs) - elif len(args) + len(kwargs) > 1: - self._post(*args, **kwargs) - - def _post(self, nsap, ttl=0): - """Create a new :class:`~dyn.tm.records.NSAPRecord` on the DynECT - System - """ - self._nsap = nsap - self._ttl = ttl - self.api_args = {'rdata': {'nsap': self._nsap}, - 'ttl': self._ttl} - self._create_record(self.api_args) - - def rdata(self): - """Return this :class:`~dyn.tm.records.NSAPRecord`'s rdata as a JSON - blob - """ - guts = super(NSAPRecord, self).rdata() - shell = {'nsap_rdata': guts} - return shell - - @property - def nsap(self): - """Hex-encoded NSAP identifier""" - return self._nsap - - @nsap.setter - def nsap(self, value): - self._nsap = value - self.api_args['rdata']['nsap'] = self._nsap - self._update_record(self.api_args) - class RPRecord(DNSRecord): """The Respnosible Person record allows an email address and some optional @@ -1746,45 +581,14 @@ class RPRecord(DNSRecord): public servers but can provide very useful contact data during diagnosis and debugging network problems. """ + record_type = 'RPRecord' - def __init__(self, zone, fqdn, *args, **kwargs): - """Create an :class:`~dyn.tm.records.RPRecord` object + #: Email address of the Responsible Person. + mbox = StringAttribute('mbox') - :param zone: Name of zone where the record will be added - :param fqdn: Name of node where the record will be added - :param mbox: Email address of the Responsible Person. - :param txtdname: Hostname where a TXT record exists with more - information on the responsible person. - :param ttl: TTL for the record in seconds - """ - if 'create' in kwargs: - super(RPRecord, self).__init__(zone, fqdn, kwargs['create']) - del kwargs['create'] - self._build(kwargs) - self._record_type = 'RPRecord' - else: - super(RPRecord, self).__init__(zone, fqdn) - self._record_type = 'RPRecord' - self._mbox = self._txtdname = None - if 'record_id' in kwargs: - self._get_record(kwargs['record_id']) - elif 'mbox' in kwargs or 'txtdname' in kwargs or 'ttl' in kwargs: - self._post(*args, **kwargs) - elif len(args) + len(kwargs) > 1: - self._post(*args, **kwargs) - - def _post(self, mbox, txtdname, ttl=0): - """Create a new :class:`~dyn.tm.records.RPRecord` on the DynECT System - """ - if '@' in mbox: - mbox = mbox.replace('@', '.') - self._mbox = mbox - self._txtdname = txtdname - self._ttl = ttl - self.api_args = {'rdata': {'mbox': self._mbox, - 'txtdname': self._txtdname}, - 'ttl': self._ttl} - self._create_record(self.api_args) + #: Hostname where a TXT record exists with more information on the + #: responsible person. + txtdname = StringAttribute('txtdname') def rdata(self): """Return this :class:`~dyn.tm.records.RPRecord`'s rdata as a JSON blob @@ -1793,107 +597,6 @@ def rdata(self): shell = {'rp_rdata': guts} return shell - @property - def mbox(self): - """Email address of the Responsible Person. Data format: Replace @ - symbol with a dot '.' in the address - """ - return self._mbox - - @mbox.setter - def mbox(self, value): - self._mbox = value - self.api_args['rdata']['mbox'] = self._mbox - self._update_record(self.api_args) - - @property - def txtdname(self): - """Hostname where a TXT record exists with more information on the - responsible person - """ - return self._txtdname - - @txtdname.setter - def txtdname(self, value): - self._txtdname = value - self.api_args['rdata']['txtdname'] = self._txtdname - self._update_record(self.api_args) - - -class NSRecord(DNSRecord): - """Nameserver Records are used to list all the nameservers that will respond - authoritatively for a domain. - """ - - def __init__(self, zone, fqdn, *args, **kwargs): - """Create an :class:`~dyn.tm.records.NSRecord` object - - :param zone: Name of zone where the record will be added - :param fqdn: Name of node where the record will be added - :param nsdname: Hostname of the authoritative Nameserver for the zone - :param service_class: Hostname of the authoritative Nameserver for the - zone - :param ttl: TTL for the record in seconds - """ - if 'create' in kwargs: - super(NSRecord, self).__init__(zone, fqdn, kwargs['create']) - del kwargs['create'] - self._build(kwargs) - self._record_type = 'NSRecord' - else: - super(NSRecord, self).__init__(zone, fqdn) - self._record_type = 'NSRecord' - self._nsdname = None - self._service_class = None - if 'record_id' in kwargs: - self._get_record(kwargs['record_id']) - elif 'nsdname' in kwargs or 'service_class' in \ - kwargs or 'ttl' in kwargs: - self._post(*args, **kwargs) - elif len(args) + len(kwargs) > 1: - self._post(*args, **kwargs) - - def _post(self, nsdname, service_class='', ttl=0): - """Create a new :class:`~dyn.tm.records.NSRecord` on the DynECT System - """ - self._nsdname = nsdname - self._service_class = service_class - self._ttl = ttl - self.api_args = {'rdata': {'nsdname': self._nsdname}, - 'ttl': self._ttl, - 'service_class': self._service_class} - self._create_record(self.api_args) - - def rdata(self): - """Return this :class:`~dyn.tm.records.NSRecord`'s rdata as a JSON blob - """ - guts = super(NSRecord, self).rdata() - shell = {'ns_rdata': guts} - return shell - - @property - def nsdname(self): - """Hostname of the authoritative Nameserver for the zone""" - return self._nsdname - - @nsdname.setter - def nsdname(self, value): - self._nsdname = value - self.api_args['rdata']['nsdname'] = self._nsdname - self._update_record(self.api_args) - - @property - def service_class(self): - """Hostname of the authoritative Nameserver for the zone""" - return self._service_class - - @service_class.setter - def service_class(self, value): - self._service_class = value - api_args = {'rdata': {'nsdname': self._nsdname}, - 'service_class': self._service_class} - self._update_record(api_args) - class SOARecord(DNSRecord): """The Start of Authority Record describes the global properties for the @@ -1901,30 +604,17 @@ class SOARecord(DNSRecord): time. NOTE: Dynect users do not have the permissions required to create or delete SOA records on the Dynect System. """ + record_type = 'SOARecord' - def __init__(self, zone, fqdn, *args, **kwargs): - """Create an :class:`~dyn.tm.records.SOARecord` object + #: Domain name which specifies the mailbox of the person responsible for + #: this zone + rname = StringAttribute('rname') - :param zone: Name of zone where the record will be added - :param fqdn: Name of node where the record will be added - """ - if 'create' in kwargs: - super(SOARecord, self).__init__(zone, fqdn, kwargs['create']) - del kwargs['create'] - self._build(kwargs) - self._record_type = 'SOARecord' - else: - super(SOARecord, self).__init__(zone, fqdn) - self._record_type = 'SOARecord' - self._rname = self._serial_style = self._minimum = None - if 'record_id' in kwargs: - self._get_record(kwargs['record_id']) - elif len(args) > 0: - self._get_record(*args) - else: - # Users can not POST or DELETE SOA Records - pass - self.api_args = {'rdata': {'rname': self._rname}} + #: The style of the zone's serial + serial_style = StringAttribute('serial_style') + + #: The minimum TTL for this :class:`~dyn.tm.records.SOARecord`'s zone + minimum = IntegerAttribute('minimum') def rdata(self): """Return this :class:`~dyn.tm.records.SOARecord`'s rdata as a JSON @@ -1934,55 +624,6 @@ def rdata(self): shell = {'soa_rdata': guts} return shell - @property - def rname(self): - """Domain name which specifies the mailbox of the person responsible - for this zone - """ - return self._rname - - @rname.setter - def rname(self, value): - self._rname = value - self.api_args['rdata']['rname'] = self._rname - self._update_record(self.api_args) - - @property - def serial_style(self): - """The style of the zone's serial""" - return self._serial_style - - @serial_style.setter - def serial_style(self, value): - self._serial_style = value - api_args = {'rdata': {'rname': self._rname}, - 'serial_style': self._serial_style} - self._update_record(api_args) - - @property - def minimum(self): - """The minimum TTL for this :class:`~dyn.tm.records.SOARecord`""" - return self._minimum - - @minimum.setter - def minimum(self, value): - self._minimum = value - api_args = {'rdata': {'rname': self._rname, 'minimum': self._minimum}} - self._update_record(api_args) - - @property - def ttl(self): - """The TTL for this record""" - return self._ttl - - @ttl.setter - def ttl(self, value): - """Set the value of this SOARecord's ttl property""" - self._ttl = value - api_args = {'rdata': {'rname': self._rname}, - 'ttl': self._ttl} - self._update_record(api_args) - def delete(self): """Users can not POST or DELETE SOA Records""" pass @@ -1993,39 +634,10 @@ class SPFRecord(DNSRecord): Transfer Agent (MTA) to verify that the originating IP of an email from a sender is authorized to send main for the sender's domain. """ + record_type = 'SPFRecord' - def __init__(self, zone, fqdn, *args, **kwargs): - """Create an :class:`~dyn.tm.records.SPFRecord` object - - :param zone: Name of zone where the record will be added - :param fqdn: Name of node where the record will be added - :param txtdata: Free text containing SPF record information - :param ttl: TTL for the record in seconds - """ - if 'create' in kwargs: - super(SPFRecord, self).__init__(zone, fqdn, kwargs['create']) - del kwargs['create'] - self._build(kwargs) - self._record_type = 'SPFRecord' - else: - super(SPFRecord, self).__init__(zone, fqdn) - self._record_type = 'SPFRecord' - self._txtdata = None - if 'record_id' in kwargs: - self._get_record(kwargs['record_id']) - elif 'txtdata' in kwargs or 'ttl' in kwargs: - self._post(*args, **kwargs) - elif len(args) + len(kwargs) > 1: - self._post(*args, **kwargs) - - def _post(self, txtdata, ttl=0): - """Create a new :class:`~dyn.tm.records.SPFRecord` on the DynECT System - """ - self._txtdata = txtdata - self._ttl = ttl - self.api_args = {'rdata': {'txtdata': self._txtdata}, - 'ttl': self._ttl} - self._create_record(self.api_args) + #: Free text containing SPF record information + txtdata = StringAttribute('txtdata') def rdata(self): """Return this :class:`~dyn.tm.records.SPFRecord`'s rdata as a JSON @@ -2035,250 +647,66 @@ def rdata(self): shell = {'spf_rdata': guts} return shell - @property - def txtdata(self): - """Free text box containing SPF record information""" - return self._txtdata - - @txtdata.setter - def txtdata(self, value): - self._txtdata = value - self.api_args['rdata']['txtdata'] = self._txtdata - self._update_record(self.api_args) - class SRVRecord(DNSRecord): """The Services Record type allow a service to be associated with a host name. A user or application that wishes to discover where a service is located can interrogate for the relevant SRV that describes the service. """ + record_type = 'SRVRecord' - def __init__(self, zone, fqdn, *args, **kwargs): - """Create a :class:`~dyn.tm.records.SRVRecord` object - - :param zone: Name of zone where the record will be added - :param fqdn: Name of node where the record will be added - :param port: Indicates the port where the service is running - :param priority: Numeric value for priority usage. Lower value takes - precedence over higher value where two records of the same type - exist on the zone/node - :param target: The domain name of a host where the service is running - on the specified port - :param weight: Secondary prioritizing of records to serve. Records of - equal priority should be served based on their weight. Higher values - are served more often - :param ttl: TTL for the record. Set to 0 to use zone default - """ - if 'create' in kwargs: - super(SRVRecord, self).__init__(zone, fqdn, kwargs['create']) - del kwargs['create'] - self._build(kwargs) - self._record_type = 'SRVRecord' - else: - super(SRVRecord, self).__init__(zone, fqdn) - self._record_type = 'SRVRecord' - self._port = self._priority = self._target = self._weight = None - if 'record_id' in kwargs: - self._get_record(kwargs['record_id']) - elif 'port' in kwargs or 'priority' in kwargs or 'target' in \ - kwargs or 'weight' in kwargs or 'ttl' in kwargs: - self._post(*args, **kwargs) - elif len(args) + len(kwargs) > 1: - self._post(*args, **kwargs) - - def _post(self, port, priority, target, weight, ttl=0): - """Create a new :class:`~dyn.tm.records.SRVRecord` on the DynECT System - """ - self._port = port - self._priority = priority - self._target = target - self._weight = weight - self._ttl = ttl - self.api_args = {'rdata': {'port': self._port, - 'priority': self._priority, - 'target': self._target, - 'weight': self._weight}, - 'ttl': self._ttl} - self._create_record(self.api_args) + #: Indicates the port where the service is running + port = IntegerAttribute('port') + + #: Numeric value for priority usage. Lower value takes precedence over + #: higher value where two records of the same type exist on the zone/node + priority = IntegerAttribute('priority') + + #: The domain name of a host where the service is running on the specified + #: port + target = StringAttribute('target') + + #: Secondary prioritizing of records to serve. Records of equal priority + #: should be served based on their weight. Higher values are served more + #: often + weight = IntegerAttribute('weight') def rdata(self): - """Return this :class:`~dyn.tm.records.SRVRecord`'s rdata as a JSON - blob - """ + """Return this :class:`~dyn.tm.records.SRVRecord`'s rdata as JSON""" guts = super(SRVRecord, self).rdata() shell = {'srv_rdata': guts} return shell - @property - def port(self): - """Indicates the port where the service is running""" - return self._port - - @port.setter - def port(self, value): - self._port = value - self.api_args['rdata']['port'] = self._port - self._update_record(self.api_args) - - @property - def priority(self): - """Numeric value for priority usage. Lower value takes precedence over - higher value where two records of the same type exist on the zone/node - """ - return self._priority - - @priority.setter - def priority(self, value): - self._priority = value - self.api_args['rdata']['priority'] = self._priority - self._update_record(self.api_args) - @property - def target(self): - """The domain name of a host where the service is running on the - specified `port` - """ - return self._target - - @target.setter - def target(self, value): - self._target = value - self.api_args['rdata']['target'] = self._target - self._update_record(self.api_args) - - @property - def weight(self): - """Secondary prioritizing of records to serve. Records of equal - priority should be served based on their weight. Higher values are - served more often - """ - return self._weight +class TLSARecord(DNSRecord): + """The TLSA record is used to associate a TLS server certificate or public + key with the domain name where the record is found, thus forming a + "TLSA certificate association". Defined in RFC 6698 + """ + record_type = 'TLSARecord' - @weight.setter - def weight(self, value): - self._weight = value - self.api_args['rdata']['weight'] = self._weight - self._update_record(self.api_args) + #: Specifies the provided association that will be used to match the + #: certificate presented in the TLS handshake + cert_usage = IntegerAttribute('cert_usage') + #: Specifies which part of the TLS certificate presented by the server will + #: be matched against the association data. + selector = IntegerAttribute('selector') -class TLSARecord(DNSRecord): - """The TLSA record is used to associate a TLS server - certificate or public key with the domain name where the record is - found, thus forming a "TLSA certificate association". Defined in RFC 6698 - """ + #: Specifies how the certificate association is presented. + match_type = IntegerAttribute('match_type') - def __init__(self, zone, fqdn, *args, **kwargs): - """Create an :class:`~dyn.tm.records.TLSARecord` object - - :param zone: Name of zone where the record will be added - :param fqdn: Name of node where the record will be added - :param cert_usage: Specifies the provided association that will be used - to match the certificate presented in the TLS handshake. Example - values: 0 (CA constraint), 1 (Service certificate constraint), - 2 (Trust anchor assertion ), 3 (Domain-issued certificate) - :param selector: Specifies which part of the TLS certificate presented - by the server will be matched against the association data. Example - values: 0 (Full certificate), 1 (SubjectPublicKeyInfo) - :param match_type: Specifies how the certificate association is - presented. Example values: 0 (No hash used), 1 (SHA-256), - 2 (SHA-512) - :param certificate: Full certificate or its SubjectPublicKeyInfo, or - hash based on the matching type. - :param ttl: TTL for the record in seconds - """ - if 'create' in kwargs: - super(TLSARecord, self).__init__(zone, fqdn, kwargs['create']) - del kwargs['create'] - self._build(kwargs) - self._record_type = 'TLSARecord' - else: - super(TLSARecord, self).__init__(zone, fqdn) - self._record_type = 'TLSARecord' - self._cert_usage = self._selector = None - self._mathc_type = self._certificate = None - if 'record_id' in kwargs: - self._get_record(kwargs['record_id']) - elif len(args) + len(kwargs) == 1: - self._get_record(*args, **kwargs) - elif 'cert_usage' in kwargs or 'selector' in \ - kwargs or 'match_type' in kwargs or \ - 'certificate' in kwargs or 'ttl' in kwargs: - self._post(*args, **kwargs) - elif len(args) + len(kwargs) >= 1: - self._post(*args, **kwargs) - - def _post(self, cert_usage, selector, match_type, certificate, ttl=0): - """Create a new :class:`~dyn.tm.records.TLSARecord` on the DynECT System - """ - self._ttl = ttl - self._cert_usage = cert_usage - self._selector = selector - self._match_type = match_type - self._certificate = certificate - self.api_args = {'rdata': {'cert_usage': self._cert_usage, - 'selector': self._selector, - 'match_type': self._match_type, - 'certificate': self._certificate}, - 'ttl': self._ttl} - self._create_record(self.api_args) + #: Full certificate or its SubjectPublicKeyInfo, or hash based on the + #: matching type + certificate = StringAttribute('certificate') def rdata(self): - """Return this :class:`~dyn.tm.records.TLSARecord`'s rdata as a JSON blob + """Return this :class:`~dyn.tm.records.TLSARecord`'s rdata as a JSON + blob """ guts = super(TLSARecord, self).rdata() shell = {'tlsa_rdata': guts} return shell - - @property - def cert_usage(self): - """Specifies the provided association that will be used - to match the certificate presented in the TLS handshake - """ - return self._cert_usage - - @cert_usage.setter - def cert_usage(self, value): - self._cert_usage = value - self.api_args['rdata']['cert_usage'] = self._cert_usage - self._update_record(self.api_args) - - @property - def selector(self): - """Specifies which part of the TLS certificate presented - by the server will be matched against the association data. - """ - return self._selector - - @selector.setter - def selector(self, value): - self._selector = value - self.api_args['rdata']['selector'] = self._selector - self._update_record(self.api_args) - - @property - def match_type(self): - """Specifies how the certificate association is presented. - """ - return self._match_type - - @match_type.setter - def match_type(self, value): - self._match_type = value - self.api_args['rdata']['match_type'] = self._match_type - self._update_record(self.api_args) - - @property - def certificate(self): - """Full certificate or its SubjectPublicKeyInfo, or - hash based on the matching type - """ - return self._certificate - - @certificate.setter - def certificate(self, value): - self._certificate = value - self.api_args['rdata']['certificate'] = self._certificate - self._update_record(self.api_args) class TXTRecord(DNSRecord): @@ -2286,40 +714,10 @@ class TXTRecord(DNSRecord): with a name. For example, it can be used to provide a description of the host, service contacts, or any other required system information. """ + record_type = 'TXTRecord' - def __init__(self, zone, fqdn, *args, **kwargs): - """Create a new TXTRecord object - - :param zone: Name of zone where the record will be added - :param fqdn: Name of node where the record will be added - :param txtdata: Free form text for this - :class:`~dyn.tm.records.TXTRecord` - :param ttl: TTL for the record. Set to 0 to use zone default - """ - if 'create' in kwargs: - super(TXTRecord, self).__init__(zone, fqdn, kwargs['create']) - del kwargs['create'] - self._build(kwargs) - self._record_type = 'TXTRecord' - else: - super(TXTRecord, self).__init__(zone, fqdn) - self._record_type = 'TXTRecord' - self._txtdata = None - if 'record_id' in kwargs: - self._get_record(kwargs['record_id']) - elif 'txtdata' in kwargs or 'ttl' in kwargs: - self._post(*args, **kwargs) - elif len(args) + len(kwargs) == 2: - self._post(*args, **kwargs) - - def _post(self, txtdata, ttl=0): - """Create a new :class:`~dyn.tm.records.TXTRecord` on the DynECT System - """ - self._ttl = ttl - self._txtdata = txtdata - self.api_args = {'rdata': {'txtdata': self._txtdata}, - 'ttl': self._ttl} - self._create_record(self.api_args) + #: Free form text + txtdata = StringAttribute('txtdata') def rdata(self): """Return this :class:`~dyn.tm.records.TXTRecord`'s rdata as a JSON @@ -2329,13 +727,12 @@ def rdata(self): shell = {'txt_rdata': guts} return shell - @property - def txtdata(self): - """Free form text""" - return self._txtdata - - @txtdata.setter - def txtdata(self, value): - self._txtdata = value - self.api_args['rdata']['txtdata'] = self._txtdata - self._update_record(self.api_args) +RECORD_TYPES = { + 'A': ARecord, 'AAAA': AAAARecord, 'CERT': CERTRecord, 'CNAME': CNAMERecord, + 'DHCID': DHCIDRecord, 'DNAME': DNAMERecord, 'DNSKEY': DNSKEYRecord, + 'DS': DSRecord, 'KEY': KEYRecord, 'KX': KXRecord, 'LOC': LOCRecord, + 'IPSECKEY': IPSECKEYRecord, 'MX': MXRecord, 'NAPTR': NAPTRRecord, + 'PTR': PTRRecord, 'PX': PXRecord, 'NSAP': NSAPRecord, 'RP': RPRecord, + 'NS': NSRecord, 'SOA': SOARecord, 'SPF': SPFRecord, 'SRV': SRVRecord, + 'TLSA': TLSARecord, 'TXT': TXTRecord +} diff --git a/dyn/tm/reports.py b/dyn/tm/reports.py index 3683cdc..b8feca4 100644 --- a/dyn/tm/reports.py +++ b/dyn/tm/reports.py @@ -23,8 +23,7 @@ def get_check_permission(permission, zone_name=None): api_args = {'permission': permission} if zone_name is not None: api_args['zone_name'] = zone_name - response = DynectSession.get_session().execute('/CheckPermissionReport/', - 'POST', api_args) + response = DynectSession.post('/CheckPermissionReport/', api_args) return response['data'] @@ -47,8 +46,7 @@ def get_dnssec_timeline(zone_name, start_ts=None, end_ts=None): api_args['end_ts'] = unix_date(end_ts) elif end_ts is None and start_ts is not None: api_args['end_ts'] = unix_date(datetime.now()) - response = DynectSession.get_session().execute('/DNSSECTimelineReport/', - 'POST', api_args) + response = DynectSession.post('/DNSSECTimelineReport/', api_args) return response['data'] @@ -65,12 +63,9 @@ def get_rttm_log(zone_name, fqdn, start_ts, end_ts=None): :return: A *dict* containing log report data """ end_ts = end_ts or datetime.now() - api_args = {'zone': zone_name, - 'fqdn': fqdn, - 'start_ts': unix_date(start_ts), - 'end_ts': unix_date(end_ts)} - response = DynectSession.get_session().execute('/RTTMLogReport/', - 'POST', api_args) + api_args = {'zone': zone_name, 'fqdn': fqdn, + 'start_ts': unix_date(start_ts), 'end_ts': unix_date(end_ts)} + response = DynectSession.post('/RTTMLogReport/', api_args) return response['data'] @@ -84,11 +79,8 @@ def get_rttm_rrset(zone_name, fqdn, ts): report :return: A *dict* containing rrset report data """ - api_args = {'zone': zone_name, - 'fqdn': fqdn, - 'ts': unix_date(ts)} - response = DynectSession.get_session().execute('/RTTMRRSetReport/', - 'POST', api_args) + api_args = {'zone': zone_name, 'fqdn': fqdn, 'ts': unix_date(ts)} + response = DynectSession.post('/RTTMRRSetReport/', api_args) return response['data'] @@ -108,8 +100,7 @@ def get_qps(start_ts, end_ts=None, breakdown=None, hosts=None, rrecs=None, :return: A *str* with CSV data """ end_ts = end_ts or datetime.now() - api_args = {'start_ts': unix_date(start_ts), - 'end_ts': unix_date(end_ts)} + api_args = {'start_ts': unix_date(start_ts), 'end_ts': unix_date(end_ts)} if breakdown is not None: api_args['breakdown'] = breakdown if hosts is not None: @@ -118,8 +109,7 @@ def get_qps(start_ts, end_ts=None, breakdown=None, hosts=None, rrecs=None, api_args['rrecs'] = rrecs if zones is not None: api_args['zones'] = zones - response = DynectSession.get_session().execute('/QPSReport/', - 'POST', api_args) + response = DynectSession.post('/QPSReport/', api_args) return response['data'] @@ -137,6 +127,5 @@ def get_zone_notes(zone_name, offset=None, limit=None): api_args['offset'] = offset if limit: api_args['limit'] = limit - response = DynectSession.get_session().execute('/ZoneNoteReport/', - 'POST', api_args) + response = DynectSession.post('/ZoneNoteReport/', api_args) return response['data'] diff --git a/dyn/tm/services/_shared.py b/dyn/tm/services/_shared.py new file mode 100644 index 0000000..1a8fb7a --- /dev/null +++ b/dyn/tm/services/_shared.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +from ..session import DynectSession, DNSAPIObject +from ...core import StringAttribute, ValidatedAttribute, IntegerAttribute +from ...compat import force_unicode + +__author__ = 'jnappi' + + +class BaseMonitor(DNSAPIObject): + """A :class:`Monitor` for a GSLB Service""" + protocol = ValidatedAttribute('protocol', + validator=('HTTP', 'HTTPS', 'PING', 'SMTP', + 'TCP')) + interval = ValidatedAttribute('interval', validator=(1, 5, 10, 15)) + retries = IntegerAttribute('retries') + timeout = ValidatedAttribute('timeout', validator=(10, 15, 25, 30)) + port = IntegerAttribute('port') + path = StringAttribute('path') + host = StringAttribute('host') + header = StringAttribute('header') + expected = StringAttribute('expected') + + def __init__(self, protocol, interval, retries=None, timeout=None, + port=None, path=None, host=None, header=None, expected=None): + """Create a :class:`Monitor` object + + :param protocol: The protocol to monitor. Must be either HTTP, HTTPS, + PING, SMTP, or TCP + :param interval: How often (in minutes) to run the monitor. Must be 1, + 5, 10, or 15, + :param retries: The number of retries the monitor attempts on failure + before giving up + :param timeout: The amount of time in seconds before the connection + attempt times out + :param port: For HTTP(S)/SMTP/TCP probes, an alternate connection port + :param path: For HTTP(S) probes, a specific path to request + :param host: For HTTP(S) probes, a value to pass in to the Host + :param header: For HTTP(S) probes, additional header fields/values to + pass in, separated by a newline character. + :param expected: For HTTP(S) probes, a string to search for in the + response. For SMTP probes, a string to compare the banner against. + Failure to find this string means the monitor will report a down + status. + """ + super(BaseMonitor, self).__init__() + self._protocol = protocol + self._interval = interval + self._retries = retries + self._timeout = timeout + self._port = port + self._path = path + self._host = host + self._header = header + self._expected = expected + self.zone = None + self.fqdn = None + + def _post(self, *args, **kwargs): + """You can't create a HealthMonitor on it's own, so force _post and + _get to be no-ops + """ + pass + _get = _post + + def _update(self, **api_args): + mon_args = {'monitor': api_args} + super(BaseMonitor, self)._update(**mon_args) + + def _build(self, data): + super(BaseMonitor, self)._build(data.pop('monitor')) + + def to_json(self): + """Convert this :class:`HealthMonitor` object to a JSON blob""" + json_blob = {'protocol': self.protocol, + 'interval': self.interval} + for key, val in self.__dict__.items(): + if val is not None and not hasattr(val, '__call__') and \ + key.startswith('_'): + json_blob[key[1:]] = val + return json_blob + + @property + def uri(self): + if self.zone is not None and self.fqdn is not None: + return '/GSLB/{0}/{1}/'.format(self.zone, self.fqdn) + raise ValueError + + @property + def status(self): + """Get the current status of this :class:`Monitor` from the + DynECT System + """ + respnose = DynectSession.get(self.uri) + return respnose['data']['status'] + + def __str__(self): + return force_unicode('<{0}>: {1}').format(self.__class__.__name__, + self.protocol) diff --git a/dyn/tm/services/active_failover.py b/dyn/tm/services/active_failover.py index 5eddbd9..3acf716 100644 --- a/dyn/tm/services/active_failover.py +++ b/dyn/tm/services/active_failover.py @@ -1,222 +1,85 @@ # -*- coding: utf-8 -*- -import logging - -from ..utils import Active -from ..errors import DynectInvalidArgumentError -from ..session import DynectSession +from ._shared import BaseMonitor +from ..utils import Active, APIList +from ..session import DynectSession, DNSAPIObject +from ...core import (APIService, ImmutableAttribute, StringAttribute, + ClassAttribute, IntegerAttribute, ValidatedListAttribute) from ...compat import force_unicode __author__ = 'jnappi' -__all__ = ['HealthMonitor', 'ActiveFailover'] +__all__ = ['AFOMonitor', 'ActiveFailover'] -class HealthMonitor(object): +class AFOMonitor(BaseMonitor): """A health monitor for an :class:`ActiveFailover` service""" - def __init__(self, protocol, interval, retries=None, timeout=None, - port=None, path=None, host=None, header=None, expected=None): - """Create a :class:`HealthMonitor` object - :param protocol: The protocol to monitor. Must be either HTTP, HTTPS, - PING, SMTP, or TCP - :param interval: How often (in minutes) to run this - :class:`HealthMonitor`. Must be 1, 5, 10, or 15, - :param retries: The number of retries the monitor attempts on failure - before giving up - :param timeout: The amount of time in seconds before the connection - attempt times out - :param port: For HTTP(S)/SMTP/TCP probes, an alternate connection port - :param path: For HTTP(S) probes, a specific path to request - :param host: For HTTP(S) probes, a value to pass in to the Host - :param header: For HTTP(S) probes, additional header fields/values to - pass in, separated by a newline character. - :param expected: For HTTP(S) probes, a string to search for in the - response. For SMTP probes, a string to compare the banner against. - Failure to find this string means the monitor will report a down - status. - """ - super(HealthMonitor, self).__init__() - self._protocol = protocol - self._interval = interval - self._retries = retries - self._timeout = timeout - self._port = port - self._path = path - self._host = host - self._header = header - self._expected = expected - self.zone = None - self.fqdn = None - self.valid_protocols = ('HTTP', 'HTTPS', 'PING', 'SMTP', 'TCP') - self.valid_intervals = (1, 5, 10, 15) - self.valid_timeouts = (10, 15, 25, 30) + @property + def uri(self): + if self.zone is not None and self.fqdn is not None: + return '/Failover/{0}/{1}/'.format(self.zone, self.fqdn) + raise ValueError - def to_json(self): - """Convert this :class:`HealthMonitor` object to a JSON blob""" - json_blob = {'protocol': self.protocol, - 'interval': self.interval} - for key, val in self.__dict__.items(): - if val is not None and not hasattr(val, '__call__') and \ - key.startswith('_'): - json_blob[key[1:]] = val - return json_blob - def __eq__(self, other): - """eq override for comparing :class:`HealthMonitor` objects to JSON - response hashes or other :class:`DNSSECKey` instances +class ActiveFailover(APIService, DNSAPIObject): + """With Active Failover, we monitor your Primary IP. If a failover event + is detected, our system auto switches (hot swaps) to your dedicated back-up + IP + """ + uri = '/Failover/{zone}/{fqdn}/' - :param other: the value to compare this :class:`HealthMonitor` to. Valid - input types: `dict`, :class:`HealthMonitor` - """ - if isinstance(other, dict): - return False - elif isinstance(other, HealthMonitor): - return False - else: - return False + #: The zone that this :class:`ActiveFailover` service is attached to + zone = ImmutableAttribute('zone') - @property - def status(self): - """Get the current status of this :class:`HealthMonitor` from the DynECT - System - """ - api_args = {} - uri = '/Failover/{}/{}/'.format(self.zone, self.fqdn) - respnose = DynectSession.get_session().execute(uri, 'GET', api_args) - return respnose['data']['status'] + #: The fqdn of this :class:`ActiveFailover` service is attached to + fqdn = ImmutableAttribute('fqdn') - @property - def protocol(self): - """The protocol to monitor""" - return self._protocol - @protocol.setter - def protocol(self, value): - if value not in self.valid_protocols: - raise Exception - self._protocol = value - api_args = {'monitor': {'protocol': self._protocol}} - uri = '/Failover/{}/{}/'.format(self.zone, self.fqdn) - DynectSession.get_session().execute(uri, 'PUT', api_args) + #: IPv4 Address or FQDN being monitored by this :class:`ActiveFailover` + address = StringAttribute('address') - @property - def interval(self): - """How often to run this monitor""" - return self._interval - @interval.setter - def interval(self, value): - if value not in self.valid_intervals: - raise Exception - self._interval = value - api_args = {'monitor': {'interval': self._interval}} - uri = '/Failover/{}/{}/'.format(self.zone, self.fqdn) - DynectSession.get_session().execute(uri, 'PUT', api_args) + #: The target failover resource type. + failover_mode = StringAttribute('failover_mode') - @property - def retries(self): - """The number of retries the monitor attempts on failure before giving - up - """ - return self._retries - @retries.setter - def retries(self, value): - self._retries = value - api_args = {'monitor': {'retries': self._retries}} - uri = '/Failover/{}/{}/'.format(self.zone, self.fqdn) - DynectSession.get_session().execute(uri, 'PUT', api_args) + #: The IPv4 Address or CNAME data for the failover target + failover_data = StringAttribute('failover_data') - @property - def timeout(self): - """The amount of time in seconds before the connection attempt times - out - """ - return self._timeout - @timeout.setter - def timeout(self, value): - self._timeout = value - api_args = {'monitor': {'timeout': self._timeout}} - uri = '/Failover/{}/{}/'.format(self.zone, self.fqdn) - DynectSession.get_session().execute(uri, 'PUT', api_args) + #: The :class:`AFOMonitor` for this :class:`ActiveFailover` service + monitor = ClassAttribute('monitor', AFOMonitor) - @property - def port(self): - """For HTTP(S)/SMTP/TCP probes, an alternate connection port""" - return self._port - @port.setter - def port(self, value): - self._port = value - api_args = {'monitor': {'port': self._port}} - uri = '/Failover/{}/{}/'.format(self.zone, self.fqdn) - DynectSession.get_session().execute(uri, 'PUT', api_args) + #: Name of contact to receive notifications for this service + contact_nickname = StringAttribute('contact_nickname') - @property - def path(self): - """For HTTP(S) probes, a specific path to request""" - return self._path - @path.setter - def path(self, value): - self._path = value - api_args = {'monitor': {'path': self._path}} - uri = '/Failover/{}/{}/'.format(self.zone, self.fqdn) - DynectSession.get_session().execute(uri, 'PUT', api_args) + #: Indicates whether this service should restore its original state when + #: the source IPs resume online status + auto_recover = StringAttribute('auto_recover') - @property - def host(self): - """For HTTP(S) probes, a value to pass in to the Host""" - return self._host - @host.setter - def host(self, value): - self._host = value - api_args = {'monitor': {'host': self._host}} - uri = '/Failover/{}/{}/'.format(self.zone, self.fqdn) - DynectSession.get_session().execute(uri, 'PUT', api_args) + #: A comma separated list of what events trigger notifications. Must be one + #: of 'ip', 'svc', or 'nosrv' + notify_events = ValidatedListAttribute('notify_events', + validator=('ip', 'svc', 'nosrv')) - @property - def header(self): - """For HTTP(S) probes, additional header fields/values to pass in, - separated by a newline character - """ - return self._header - @header.setter - def header(self, value): - self._header = value - api_args = {'monitor': {'header': self._header}} - uri = '/Failover/{}/{}/'.format(self.zone, self.fqdn) - DynectSession.get_session().execute(uri, 'PUT', api_args) + #: The Hostname or IP address of a server to receive syslog notifications + #: on monitoring events + syslog_server = StringAttribute('syslog_server') - @property - def expected(self): - """For HTTP(S) probes, a string to search for in the response. For - SMTP probes, a string to compare the banner against. Failure to find - this string means the monitor will report a down status - """ - return self._expected - @expected.setter - def expected(self, value): - self._expected = value - api_args = {'monitor': {'expected': self._expected}} - uri = '/Failover/{}/{}/'.format(self.zone, self.fqdn) - DynectSession.get_session().execute(uri, 'PUT', api_args) + #: The port where the remote syslog server listens + syslog_port = IntegerAttribute('syslog_port') - def __str__(self): - """str override""" - return force_unicode(': {}').format(self._protocol) - __repr__ = __unicode__ = __str__ + #: The ident to use when sending syslog notifications + syslog_ident = StringAttribute('syslog_ident') - def __bytes__(self): - """bytes override""" - return bytes(self.__str__()) + #: The syslog facility to use when sending syslog notifications + syslog_facility = StringAttribute('syslog_facility') + #: TTL in seconds of records in the service. Must be less than 1/2 of the + #: :class:`AFOMonitor`'s monitoring interval + ttl = IntegerAttribute('ttl') -class ActiveFailover(object): - """With Active Failover, we monitor your Primary IP. If a failover event - is detected, our system auto switches (hot swaps) to your dedicated back-up - IP - """ def __init__(self, zone, fqdn, *args, **kwargs): """Create a new :class:`ActiveFailover` object :param zone: The zone to attach this :class:`ActiveFailover` service to - :param fqdn: The FQDN where this :class:`ActiveFailover` service will be - attached + :param fqdn: The FQDN where this :class:`ActiveFailover` service will + be attached :param address: IPv4 Address or FQDN being monitored by this :class:`ActiveFailover` service :param failover_mode: Indicates the target failover resource type. @@ -232,38 +95,16 @@ def __init__(self, zone, fqdn, *args, **kwargs): :param syslog_ident: The ident to use when sending syslog notifications :param syslog_facility: The syslog facility to use when sending syslog notifications - :param monitor: The :class:`HealthMonitor` for this + :param monitor: The :class:`AFOMonitor` for this :class:`ActiveFailover` service :param contact_nickname: Name of contact to receive notifications from this :class:`ActiveFailover` service :param ttl: Time To Live in seconds of records in the service. Must be less than 1/2 of the Health Probe's monitoring interval """ - super(ActiveFailover, self).__init__() - self.valid_notify_events = ('ip', 'svc', 'nosrv') - self._zone = zone - self._fqdn = fqdn - self._address = self._failover_mode = self._failover_data = None - self._monitor = self._active = None - self._contact_nickname = self._auto_recover = self._notify_events = None - self._syslog_server = self._syslog_port = self._syslog_ident = None - self._syslog_facility = self._ttl = None - self.uri = '/Failover/{}/{}/'.format(self._zone, self._fqdn) - self.api_args = {} - if 'api' in kwargs: - del kwargs['api'] - self._build(kwargs) - elif len(args) == 0 and len(kwargs) == 0: - self._get() - else: - self._post(*args, **kwargs) - - def _get(self): - """Build an object around an existing DynECT Active Failover Service""" - api_args = {} - response = DynectSession.get_session().execute(self.uri, 'GET', - api_args) - self._build(response['data']) + self._zone, self._fqdn = zone, fqdn + self.uri = self.uri.format(zone=zone, fqdn=fqdn) + super(ActiveFailover, self).__init__(*args, **kwargs) def _post(self, address, failover_mode, failover_data, monitor, contact_nickname, auto_recover=None, notify_events=None, @@ -274,8 +115,8 @@ def _post(self, address, failover_mode, failover_data, monitor, self._failover_mode = failover_mode self._failover_data = failover_data self._monitor = monitor - self._monitor.zone = self._zone - self._monitor.fqdn = self._fqdn + self._monitor.zone = self.zone + self._monitor.fqdn = self.fqdn self._contact_nickname = contact_nickname self._auto_recover = auto_recover self._notify_events = notify_events @@ -284,246 +125,43 @@ def _post(self, address, failover_mode, failover_data, monitor, self._syslog_ident = syslog_ident self._syslog_facility = syslog_facility self._ttl = ttl - api_args = {'address': self._address, - 'failover_mode': self._failover_mode, - 'failover_data': self._failover_data} - for key, val in self.__dict__.items(): - if val is not None and not hasattr(val, '__call__') and \ - key.startswith('_'): - if key != '_user_name': - api_args[key] = val - self.api_args = {'address': self._address, - 'failover_mode': self._failover_mode, - 'failover_data': self._failover_data, - 'monitor': self.monitor.to_json(), - 'contact_nickname': self._contact_nickname} - response = DynectSession.get_session().execute(self.uri, 'POST', - self.api_args) - self._build(response['data']) - def _build(self, data): - """Build this object from the data returned in an API response""" - for key, val in data.items(): - if key == 'monitor': - self._monitor = HealthMonitor(**val) - elif key == 'active': - self._active = Active(val) - else: - setattr(self, '_' + key, val) - - def _update(self, api_args): - """Update this :class:`ActiveFailover`, via the API, with the args in - api_args - """ - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) + response = DynectSession.post(self.uri, self.api_args) self._build(response['data']) - @property - def zone(self): - """The zone to attach this :class:`ActiveFailover` service to""" - return self._zone - @zone.setter - def zone(self, value): - pass - - @property - def fqdn(self): - """The FQDN where this :class:`ActiveFailover` service will be attached - """ - return self._fqdn - @fqdn.setter - def fqdn(self, value): - pass - - @property - def active(self): - """Return whether or not this :class:`ActiveFailover` service is active. - When setting directly, rather than using activate/deactivate valid - arguments are 'Y' or True to activate, or 'N' or False to deactivate. - Note: If your service is already active and you try to activate it, - nothing will happen. And vice versa for deactivation. - - :returns: An :class:`Active` object representing the current state of - this :class:`ActiveFailover` Service - """ - self._get() - return self._active - @active.setter - def active(self, value): - deactivate = ('N', False) - activate = ('Y', True) - if value in deactivate and self.active: - self.deactivate() - elif value in activate and not self.active: - self.activate() - - @property - def address(self): - """IPv4 Address or FQDN being monitored by this :class:`ActiveFailover` - service - """ - return self._address - @address.setter - def address(self, value): - self._address = value - api_args = {'address': self._address, - 'failover_mode': self._failover_mode, - 'failover_data': self._failover_data, - 'monitor': self.monitor.to_json(), - 'contact_nickname': self._contact_nickname} - self._update(api_args) - - @property - def failover_mode(self): - """Indicates the target failover resource type.""" - return self._failover_mode - @failover_mode.setter - def failover_mode(self, value): - self._failover_mode = value - self.api_args['failover_mode'] = self._failover_mode - self._update(self.api_args) - - @property - def failover_data(self): - """The IPv4 Address or CNAME data for the failover target""" - return self._failover_data - @failover_data.setter - def failover_data(self, value): - self._failover_data = value - self.api_args['failover_data'] = self._failover_data - self._update(self.api_args) - - @property - def monitor(self): - """The :class:`HealthMonitor` for this :class:`ActiveFailover` service - """ - return self._monitor - @monitor.setter - def monitor(self, value): - self._monitor = value - self.api_args['monitor'] = self._monitor.to_json() - self._update(self.api_args) - - @property - def contact_nickname(self): - """Name of contact to receive notifications from this - :class:`ActiveFailover` service - """ - return self._contact_nickname - @contact_nickname.setter - def contact_nickname(self, value): - self._contact_nickname = value - self.api_args['contact_nickname'] = self._contact_nickname - self._update(self.api_args) - - @property - def auto_recover(self): - """Indicates whether this service should restore its original state when - the source IPs resume online status - """ - return self._auto_recover - @auto_recover.setter - def auto_recover(self, value): - self._auto_recover = value - api_args = self.api_args - api_args['auto_recover'] = self._auto_recover - self._update(api_args) - - @property - def notify_events(self): - """A comma separated list of what events trigger notifications""" - return self._notify_events - @notify_events.setter - def notify_events(self, value): - for val in value: - if val not in self.valid_notify_events: - raise DynectInvalidArgumentError('notify_events', val, - self.valid_notify_events) - value = ', '.join(value) - api_args = self.api_args - api_args['notify_events'] = value - self._update(api_args) - - @property - def syslog_server(self): - """The Hostname or IP address of a server to receive syslog - notifications on monitoring events - """ - return self._syslog_server - @syslog_server.setter - def syslog_server(self, value): - self._syslog_server = value - api_args = self.api_args - api_args['syslog_server'] = self._syslog_server - self._update(api_args) - - @property - def syslog_port(self): - """The port where the remote syslog server listens""" - return self._syslog_port - @syslog_port.setter - def syslog_port(self, value): - self._syslog_port = value - api_args = self.api_args - api_args['syslog_port'] = self._syslog_port - self._update(api_args) - - @property - def syslog_ident(self): - """The ident to use when sending syslog notifications""" - return self._syslog_ident - @syslog_ident.setter - def syslog_ident(self, value): - self._syslog_ident = value - api_args = self.api_args - api_args['syslog_ident'] = self._syslog_ident - self._update(api_args) - - @property - def syslog_facility(self): - """The syslog facility to use when sending syslog notifications""" - return self._syslog_facility - @syslog_facility.setter - def syslog_facility(self, value): - self._syslog_facility = value - api_args = self.api_args - api_args['syslog_facility'] = self._syslog_facility - self._update(api_args) - - @property - def ttl(self): - """Time To Live in seconds of records in the service. Must be less than - 1/2 of the Health Probe's monitoring interval - """ - return self._ttl - @ttl.setter - def ttl(self, value): - self._ttl = value - api_args = self.api_args - api_args['ttl'] = self._ttl - self._update(api_args) - - def activate(self): - """Activate this :class:`ActiveFailover` service""" - api_args = {'activate': True} - self._update(api_args) - - def deactivate(self): - """Deactivate this :class:`ActiveFailover` service""" - api_args = {'deactivate': True} - self._update(api_args) + def _update(self, **api_args): + if 'monitor' in api_args: + api_args['monitor'] = api_args['monitor'].to_json() + if 'notify_events' in api_args: + api_args['notify_events'] = ', '.join(api_args['notify_events']) + if 'activate' not in api_args and 'deactivate' not in api_args: + for key, val in self.api_args.items(): + if key not in api_args: + api_args[key] = val + super(ActiveFailover, self)._update(**api_args) - def delete(self): - """Delete this :class:`ActiveFailover` service from the Dynect System""" - api_args = {} - DynectSession.get_session().execute(self.uri, 'DELETE', api_args) + def _build(self, data): + """Build this object from the data returned in an API response""" + if 'monitor' in data: + self._monitor = AFOMonitor(**data.pop('monitor')) + if 'active' in data: + self._active = Active(data.pop('active')) + if 'notify_events' in data: + events = data.pop('notify_events').split(',') + filtered = [event.strip() for event in events if event.strip()] + self._notify_events = APIList(DynectSession, 'notify_events', + self.uri, filtered) + super(ActiveFailover, self)._build(data) + + @property + def api_args(self): + """AFO's required API fields are pretty excessive per call, so use a + property to dynamically generate them on access + """ + return {'address': self.address, 'failover_mode': self.failover_mode, + 'failover_data': self.failover_data, + 'monitor': self.monitor.to_json(), + 'contact_nickname': self.contact_nickname} def __str__(self): - """str override""" - return force_unicode(': {}').format(self._fqdn) - __repr__ = __unicode__ = __str__ - - def __bytes__(self): - """bytes override""" - return bytes(self.__str__()) + return force_unicode(': {0}').format(self.fqdn) diff --git a/dyn/tm/services/ddns.py b/dyn/tm/services/ddns.py index 383c218..a64def3 100644 --- a/dyn/tm/services/ddns.py +++ b/dyn/tm/services/ddns.py @@ -1,22 +1,40 @@ # -*- coding: utf-8 -*- """This module contains API Wrapper implementations of the Dynamic DNS service """ -import logging - from ..utils import Active -from ..session import DynectSession +from ..session import DynectSession, DNSAPIObject from ..accounts import User +from ...core import APIService, ImmutableAttribute, StringAttribute from ...compat import force_unicode __author__ = 'jnappi' __all__ = ['DynamicDNS'] -class DynamicDNS(object): +class DynamicDNS(APIService, DNSAPIObject): """DynamicDNS is a service which aliases a dynamic IP Address to a static hostname """ - def __init__(self, zone, fqdn, *args, **kwargs): + uri = '/DDNS/{zone}/{fqdn}/{rr_type}/' + + #: The zone to attach this :class:`DynamicDNS` Service to + zone = ImmutableAttribute('zone') + + #: The FQDN of the node where this service will be attached + fqdn = ImmutableAttribute('fqdn') + + #: Either A, for IPv4, or AAAA, for IPv6 + record_type = ImmutableAttribute('record_type') + + #: IPv4 (if `record_type` is A) or IPv6 (if `record_type` is AAAA) address + #: for the service + address = StringAttribute('address') + + #: Name of the user to create, or the name of an existing update user to + #: allow access to this service + user = ImmutableAttribute('user') + + def __init__(self, zone, fqdn, record_type, *args, **kwargs): """Create a new :class:`DynamicDNS` service object :param zone: The zone to attach this DDNS Service to @@ -27,172 +45,35 @@ def __init__(self, zone, fqdn, *args, **kwargs): :param user: Name of the user to create, or the name of an existing update user to allow access to this service """ - super(DynamicDNS, self).__init__() - self._zone = zone - self._fqdn = fqdn - self._record_type = self._address = self.uri = self._user = None self._active = None - if 'api' in kwargs: - del kwargs['api'] - for key, val in kwargs.items(): - if key == 'active': - self._active = Active(val) - else: - setattr(self, '_' + key, val) - elif len(args) + len(kwargs) == 1: - self._get(*args, **kwargs) - elif 'record_type' in kwargs and len(kwargs) == 1: - self._get(*args, **kwargs) - else: - self._post(*args, **kwargs) - - def _get(self, record_type=None): - """Build an object around an existing DynECT DynamicDNS Service""" - self._record_type = record_type - self.uri = '/DDNS/{}/{}/{}/'.format(self._zone, self._fqdn, - self._record_type) - api_args = {} - response = DynectSession.get_session().execute(self.uri, 'GET', - api_args) - for key, val in response['data'].items(): - if key == 'active': - self._active = Active(val) - else: - setattr(self, '_' + key, val) - - def _post(self, record_type, address, user=None): + self.uri = self.uri.format(zone=zone, fqdn=fqdn, rr_type=record_type) + self.zone = zone + self.fqdn = fqdn + super(DynamicDNS, self).__init__(*args, **kwargs) + + def _build(self, data): + if 'active' in data: + self._active = Active(data.pop('active')) + if 'ddns' in data and 'new_user' in data: + for ddns_key, ddns_val in data.pop('ddns'): + setattr(self, '_' + ddns_key, ddns_val) + user_data = data.pop('new_user') + username = user_data.pop('user_name') + self._user = User(data.pop(username, api=False, **user_data)) + super(DynamicDNS, self)._build(data) + + def _post(self, address, user=None): """Create a new DynamicDNS Service on the DynECT System""" - self._record_type = record_type - self._address = address + api_args = {'address': address} if user: - self._user = user - self.uri = '/DDNS/{}/{}/{}/'.format(self._zone, self._fqdn, - self._record_type) - api_args = {'address': self._address} - if user: - api_args['user'] = self._user + api_args['user'] = user api_args['full_setup'] = True - response = DynectSession.get_session().execute(self.uri, 'POST', - api_args) - for key, val in response['data'].items(): - if user: - if key == 'ddns': - for ddns_key, ddns_val in val.items(): - setattr(self, '_' + ddns_key, ddns_val) - if key == 'new_user': - user_name = val['user_name'] - del val['user_name'] - self._user = User(user_name, api=False, **val) - elif key == 'active': - self._active = Active(val) - else: - setattr(self, '_' + key, val) - - @property - def zone(self): - """The zone that this DynamicDNS Service is attached to is a read-only - attribute - """ - return self._zone - @zone.setter - def zone(self, value): - pass - - @property - def fqdn(self): - """The fqdn that this DynamicDNS Service is attached to is a read-only - attribute - """ - return self._fqdn - @fqdn.setter - def fqdn(self, value): - pass - - @property - def active(self): - """Returns whether or not this :class:`DynamicDNS` Service is currently - active. When setting directly, rather than using activate/deactivate - valid arguments are 'Y' or True to activate, or 'N' or False to - deactivate. Note: If your service is already active and you try to - activate it, nothing will happen. And vice versa for deactivation. - - :returns: An :class:`Active` object representing the current state of - this :class:`DynamicDNS` Service - """ - return self._active - @active.setter - def active(self, value): - deactivate = ('N', False) - activate = ('Y', True) - if value in deactivate and self.active: - self.deactivate() - elif value in activate and not self.active: - self.activate() - - @property - def record_type(self): - """The record_type of a DDNS Service is a read-only attribute""" - return self._record_type - @record_type.setter - def record_type(self, value): - pass - - @property - def user(self): - """The :class:`User` attribute of a DDNS Service is a read-only - attribute""" - return self._user - @user.setter - def user(self, value): - pass - - @property - def address(self): - """IPv4 or IPv6 address for this DynamicDNS service""" - return self._address - @address.setter - def address(self, value): - self._address = value - api_args = {'address': self._address} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - for key, val in response['data'].items(): - setattr(self, '_' + key, val) - - def activate(self): - """Activate this Dynamic DNS service""" - api_args = {'activate': True} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - for key, val in response['data'].items(): - setattr(self, '_' + key, val) - - def deactivate(self): - """Deactivate this Dynamic DNS service""" - api_args = {'deactivate': True} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - for key, val in response['data'].items(): - setattr(self, '_' + key, val) + resp = DynectSession.post(self.uri, api_args) + self._build(resp['data']) def reset(self): """Resets the abuse count on this Dynamic DNS service""" - api_args = {'reset': True} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - for key, val in response['data'].items(): - setattr(self, '_' + key, val) - - def delete(self): - """Delete this Dynamic DNS service from the DynECT System""" - api_args = {} - DynectSession.get_session().execute(self.uri, 'DELETE', api_args) + self._update(reset=True) def __str__(self): - """str override""" - return force_unicode(': {}').format(self._fqdn) - __repr__ = __unicode__ = __str__ - - def __bytes__(self): - """bytes override""" - return bytes(self.__str__()) + return force_unicode(': {0}').format(self.fqdn) diff --git a/dyn/tm/services/dnssec.py b/dyn/tm/services/dnssec.py index fdf5601..5cf4475 100644 --- a/dyn/tm/services/dnssec.py +++ b/dyn/tm/services/dnssec.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- -import logging from datetime import datetime from ..utils import APIList, Active, unix_date -from ..errors import DynectInvalidArgumentError -from ..session import DynectSession +from ..session import DynectSession, DNSAPIObject +from ...core import (ImmutableAttribute, StringAttribute, + ValidatedListAttribute, APIService) from ...compat import force_unicode __author__ = 'jnappi' @@ -15,7 +15,7 @@ def get_all_dnssec(): """:return: A ``list`` of :class:`DNSSEC` Services""" uri = '/DNSSEC/' api_args = {'detail': 'Y'} - response = DynectSession.get_session().execute(uri, 'GET', api_args) + response = DynectSession.get(uri, api_args) dnssecs = [] for dnssec in response['data']: zone = dnssec['zone'] @@ -53,8 +53,7 @@ def __init__(self, key_type, algorithm, bits, start_ts=None, lifetime=None, for key, val in kwargs.items(): setattr(self, key, val) - @property - def _json(self): + def to_json(self): """The JSON representation of this :class:`DNSSECKey` object""" json_blob = {'type': self.key_type, 'algorithm': self.algorithm, @@ -81,17 +80,18 @@ def _update(self, data): setattr(self, key, val) def __str__(self): - """str override""" - return force_unicode(': {}').format(self.algorithm) - __repr__ = __unicode__ = __str__ - - def __bytes__(self): - """bytes override""" - return bytes(self.__str__()) + return force_unicode(': {0}').format(self.algorithm) -class DNSSEC(object): +class DNSSEC(APIService, DNSAPIObject): """A DynECT System DNSSEC Service""" + uri = '/DNSSEC/{zone_name}/' + zone = ImmutableAttribute('zone') + contact_nickname = StringAttribute('contact_nickname') + notify_events = ValidatedListAttribute('notify_events', + validator=('create', 'expire', + 'warning')) + def __init__(self, zone, *args, **kwargs): """Create a :class:`DNSSEC` object @@ -103,20 +103,9 @@ def __init__(self, zone, *args, **kwargs): "expire" (a key was automatically expired), or "warning" (early warnings (2 weeks, 1 week, 1 day) of events) """ - super(DNSSEC, self).__init__() - self.valid_notify_events = ('create', 'expire', 'warning') - self._zone = zone - self._contact_nickname = self._notify_events = None - self._keys = APIList(DynectSession.get_session, 'keys') self._active = None - self.uri = '/DNSSEC/{}/'.format(self._zone) - if 'api' in kwargs: - del kwargs['api'] - self._build(kwargs) - elif len(args) == 0 and len(kwargs) == 0: - self._get() - else: - self._post(*args, **kwargs) + self.uri = self.uri.format(zone_name=zone) + super(DNSSEC, self).__init__(*args, **kwargs) self._keys.uri = self.uri def _post(self, keys, contact_nickname, notify_events=None): @@ -124,7 +113,7 @@ def _post(self, keys, contact_nickname, notify_events=None): self._keys += keys self._contact_nickname = contact_nickname self._notify_events = notify_events - api_args = {'keys': [key._json for key in self._keys], + api_args = {'keys': [key.to_json() for key in self._keys], 'contact_nickname': self._contact_nickname} for key, val in self.__dict__.items(): if val is not None and not hasattr(val, '__call__') and \ @@ -136,100 +125,28 @@ def _post(self, keys, contact_nickname, notify_events=None): # Need to cast to CSV for API if self._notify_events is not None: api_args['notify_events'] = ', '.join(self._notify_events) - response = DynectSession.get_session().execute(self.uri, 'POST', - api_args) - self._build(response['data']) - - def _get(self): - """Update this object from an existing :class:`DNSSEC` service from the - Dynect System. - """ - api_args = {} - response = DynectSession.get_session().execute(self.uri, 'GET', - api_args) + response = DynectSession.post(self.uri, api_args) self._build(response['data']) def _build(self, data): """Iterate over API data responses and update this object according to the data returned """ - for key, val in data.items(): - if key == 'keys': - self._keys = APIList(DynectSession.get_session, 'keys') - for key_data in val: - key_data['key_type'] = key_data['type'] - del key_data['type'] - self._keys.append(DNSSECKey(**key_data)) - elif key == 'active': - self._active = Active(val) - else: - setattr(self, '_' + key, val) - self.uri = '/DNSSEC/{}/'.format(self._zone) - self._keys.uri = self.uri - - @property - def zone(self): - """The name of the zone where this service exists. This is a read-only - property - """ - return self._zone - @zone.setter - def zone(self, value): - pass - - @property - def active(self): - """The current status of this :class:`DNSSEC` service. When setting - directly, rather than using activate/deactivate valid arguments are 'Y' - or True to activate, or 'N' or False to deactivate. Note: If your - service is already active and you try to activate it, nothing will - happen. And vice versa for deactivation. - - :returns: An :class:`Active` object representing the current state of - this :class:`DNSSEC` Service - """ - self._get() # Do a get to ensure the most up-to-date status is returned - return self._active - @active.setter - def active(self, value): - deactivate = ('N', False) - activate = ('Y', True) - if value in deactivate and self.active: - self.deactivate() - elif value in activate and not self.active: - self.activate() - - @property - def contact_nickname(self): - """Name of contact to receive notifications""" - return self._contact_nickname - @contact_nickname.setter - def contact_nickname(self, value): - self._contact_nickname = value - api_args = {'contact_nickname': self._contact_nickname} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) - - @property - def notify_events(self): - """A list of events that trigger notifications. Valid values are: - create (a new version of a key was created), expire (a key was - automatically expired), warning (early warnings (2 weeks, 1 week, 1 day) - of events) - """ - return self._notify_events - @notify_events.setter - def notify_events(self, value): - for val in value: - if val not in self.valid_notify_events: - raise DynectInvalidArgumentError('notify_events', val, - self.valid_notify_events) - value = ', '.join(value) - api_args = {'notify_events': value} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) + if 'keys' in data: + keys = data.pop('keys') + self._keys = APIList(DynectSession.get_session, 'keys') + for key in keys: + key['key_type'] = key.pop('type') + self._keys.append(DNSSECKey(**key)) + self._keys.uri = self.uri + if 'active' in data: + self._active = Active(data.pop('active')) + super(DNSSEC, self)._build(data) + + def _update(self, **api_args): + if 'notify_events' in api_args: + api_args['notify_events'] = ', '.join(api_args['notify_events']) + super(DNSSEC, self)._update(**api_args) @property def keys(self): @@ -243,24 +160,10 @@ def keys(self): @keys.setter def keys(self, value): if isinstance(value, list) and not isinstance(value, APIList): - self._keys = APIList(DynectSession.get_session, 'keys', None, value) + self._keys = APIList(DynectSession.get_session, 'keys', self.uri, + value) elif isinstance(value, APIList): self._keys = value - self._keys.uri = self.uri - - def activate(self): - """Activate this :class:`DNSSEC` service""" - api_args = {'activate': 'Y'} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) - - def deactivate(self): - """Deactivate this :class:`DNSSEC` service""" - api_args = {'deactivate': 'Y'} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) def timeline_report(self, start_ts=None, end_ts=None): """Generates a report of events this :class:`DNSSEC` service has @@ -272,7 +175,7 @@ def timeline_report(self, start_ts=None, end_ts=None): for the end of the timeline report. Defaults to datetime.datetime.now() """ - api_args = {'zone': self._zone} + api_args = {'zone': self.zone} if start_ts is not None: api_args['start_ts'] = unix_date(start_ts) if end_ts is not None: @@ -280,19 +183,8 @@ def timeline_report(self, start_ts=None, end_ts=None): elif end_ts is None and start_ts is not None: api_args['end_ts'] = unix_date(datetime.now()) uri = '/DNSSECTimelineReport/' - response = DynectSession.get_session().execute(uri, 'POST', api_args) + response = DynectSession.post(uri, api_args) return response['data'] - def delete(self): - """Delete this :class:`DNSSEC` Service from the DynECT System""" - api_args = {} - DynectSession.get_session().execute(self.uri, 'DELETE', api_args) - def __str__(self): - """str override""" - return force_unicode(': {}').format(self._zone) - __repr__ = __unicode__ = __str__ - - def __bytes__(self): - """bytes override""" - return bytes(self.__str__()) + return force_unicode(': {0}').format(self.zone) diff --git a/dyn/tm/services/dsf.py b/dyn/tm/services/dsf.py index 5d77224..eaa2525 100644 --- a/dyn/tm/services/dsf.py +++ b/dyn/tm/services/dsf.py @@ -1,30 +1,33 @@ # -*- coding: utf-8 -*- -"""This module contains wrappers for interfacing with every element of a Traffic -Director (DSF) service. +"""This module contains wrappers for interfacing with every element of a +Traffic Director (DSF) service. """ from ..utils import APIList, Active from ..errors import DynectInvalidArgumentError from ..records import * -from ..session import DynectSession +from ..session import DynectSession, DNSAPIObject +from ...core import (ImmutableAttribute, StringAttribute, IntegerAttribute, + ValidatedAttribute, APIDescriptor, BooleanAttribute, + ListAttribute) from ...compat import force_unicode __author__ = 'jnappi' __all__ = ['get_all_dsf_services', 'get_all_dsf_monitors', 'DSFARecord', - 'DSFAAAARecord', 'DSFCERTRecord', 'DSFCNAMERecord', 'DSFDHCIDRecord', - 'DSFDNAMERecord', 'DSFDNSKEYRecord', 'DSFDSRecord', 'DSFKEYRecord', - 'DSFKXRecord', 'DSFLOCRecord', 'DSFIPSECKEYRecord', 'DSFMXRecord', - 'DSFNAPTRRecord', 'DSFPTRRecord', 'DSFPXRecord', 'DSFNSAPRecord', - 'DSFRPRecord', 'DSFNSRecord', 'DSFSPFRecord', 'DSFSRVRecord', - 'DSFTXTRecord', 'DSFRecordSet', 'DSFFailoverChain', - 'DSFResponsePool', 'DSFRuleset', 'DSFMonitorEndpoint', 'DSFMonitor', - 'TrafficDirector'] + 'DSFAAAARecord', 'DSFCERTRecord', 'DSFCNAMERecord', + 'DSFDHCIDRecord', 'DSFDNAMERecord', 'DSFDNSKEYRecord', + 'DSFDSRecord', 'DSFKEYRecord', 'DSFKXRecord', 'DSFLOCRecord', + 'DSFIPSECKEYRecord', 'DSFMXRecord', 'DSFNAPTRRecord', + 'DSFPTRRecord', 'DSFPXRecord', 'DSFNSAPRecord', 'DSFRPRecord', + 'DSFNSRecord', 'DSFSPFRecord', 'DSFSRVRecord', 'DSFTXTRecord', + 'DSFRecordSet', 'DSFFailoverChain', 'DSFResponsePool', 'DSFRuleset', + 'DSFMonitorEndpoint', 'DSFMonitor', 'TrafficDirector'] def get_all_dsf_services(): """:return: A ``list`` of :class:`TrafficDirector` Services""" uri = '/DSF/' api_args = {'detail': 'Y'} - response = DynectSession.get_session().execute(uri, 'GET', api_args) + response = DynectSession.get(uri, api_args) directors = [] for dsf in response['data']: directors.append(TrafficDirector(None, api=False, **dsf)) @@ -35,15 +38,27 @@ def get_all_dsf_monitors(): """:return: A ``list`` of :class:`DSFMonitor` Services""" uri = '/DSFMonitor/' api_args = {'detail': 'Y'} - response = DynectSession.get_session().execute(uri, 'GET', api_args) + response = DynectSession.get(uri, api_args) mons = [] for dsf in response['data']: mons.append(DSFMonitor(api=False, **dsf)) return mons -class _DSFRecord(object): - """docstring for _DSFRecord""" +class _DSFRecord(DNSAPIObject): + """Base type for all DSF Records""" + uri = '' + label = StringAttribute('label') + weight = IntegerAttribute('weight') + automation = ValidatedAttribute('automation', + validator=('auto', 'auto_down', 'manual')) + endpoints = APIDescriptor('endpoints') + endpoint_up_count = IntegerAttribute('endpoint_up_count') + eligible = BooleanAttribute('eligible') + + dsf_id = ImmutableAttribute('dsf_id') + record_set_id = ImmutableAttribute('record_set_id') + def __init__(self, label=None, weight=1, automation='auto', endpoints=None, endpoint_up_count=None, eligible=True, **kwargs): """Create a :class:`_DSFRecord` object. @@ -58,55 +73,41 @@ def __init__(self, label=None, weight=1, automation='auto', endpoints=None, Record status to be 'up' :param eligible: Indicates whether or not the Record can be served """ - self.valid_automation = ('auto', 'auto_down', 'manual') - self._label = label - self._weight = weight - if automation not in self.valid_automation: - raise DynectInvalidArgumentError('automation', automation, - self.valid_automation) - self._automation = automation - self._endpoints = endpoints - self._endpoint_up_count = endpoint_up_count - self._eligible = eligible - self._dsf_id = self._record_set_id = self.uri = None - for key, val in kwargs.items(): - setattr(self, '_' + key, val) + if 'api' not in kwargs: + kwargs['api'] = False + DNSAPIObject.__init__(self, label=label, weight=weight, + automation=automation, endpoints=endpoints, + endpoint_up_count=endpoint_up_count, + eligible=eligible, **kwargs) def _post(self, dsf_id, record_set_id): """Create a new :class:`DSFRecord` on the DynECT System :param dsf_id: The unique system id for the DSF service associated with this :class:`DSFRecord` - :param record_set_id: The unique system id for the record set associated - with this :class:`DSFRecord` + :param record_set_id: The unique system id for the record set + associated with this :class:`DSFRecord` """ - self._dsf_id = dsf_id - self._record_set_id = record_set_id - self.uri = '/DSFRecord/{}/{}/'.format(self._dsf_id, self._record_set_id) + self.uri = '/DSFRecord/{0}/{1}/'.format(dsf_id, record_set_id) api_args = {} for key, val in self.__dict__.items(): if val is not None and not hasattr(val, '__call__') and \ key.startswith('_'): if key != '_dsf_id' and key != '_record_set_id': api_args[key[1:]] = val - response = DynectSession.get_session().execute(self.uri, 'POST', - api_args) - self._build(response['data']) + resp = DynectSession.post(self.uri, api_args) + self._build(resp['data']) def _get(self, dsf_id, record_set_id): """Get an existing :class:`DSFRecord` from the DynECT System :param dsf_id: The unique system id for the DSF service associated with this :class:`DSFRecord` - :param record_set_id: The unique system id for the record set associated - with this :class:`DSFRecord` + :param record_set_id: The unique system id for the record set + associated with this :class:`DSFRecord` """ - self._dsf_id = dsf_id - self._record_set_id = record_set_id - self.uri = '/DSFRecord/{}/{}/'.format(self._dsf_id, self._record_set_id) - api_args = {} - response = DynectSession.get_session().execute(self.uri, 'GET', - api_args) + self.uri = '/DSFRecord/{0}/{1}/'.format(dsf_id, record_set_id) + response = DynectSession.get(self.uri) self._build(response['data']) def _build(self, data): @@ -120,112 +121,15 @@ def _build(self, data): else: setattr(self, '_' + key, val) - @property - def dsf_id(self): - """The unique system id for the DSF service associated with this - :class:`DSFRecord` - """ - return self._dsf_id - @dsf_id.setter - def dsf_id(self, value): - pass - - @property - def record_set_id(self): - """The unique system id for the record set associated with this - :class:`DSFRecord` - """ - return self._record_set_id - @record_set_id.setter - def record_set_id(self, value): - pass - - @property - def label(self): - """A unique label for this :class:`DSFRecord`""" - return self._label - @label.setter - def label(self, value): - self._label = value - api_args = {'label': self._label} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) - - @property - def weight(self): - """Weight for this :class:`DSFRecord`""" - return self._weight - @weight.setter - def weight(self, value): - self._weight = value - api_args = {'weight': self._weight} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) - - @property - def automation(self): - """Defines how eligible can be changed in response to monitoring. Must - be one of 'auto', 'auto_down', or 'manual' - """ - return self._automation - @automation.setter - def automation(self, value): - self._automation = value - api_args = {'automation': self._automation} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) - - @property - def endpoints(self): - """Endpoints are used to determine status, torpidity, and eligible in - response to monitor data - """ - return self._endpoints - @endpoints.setter - def endpoints(self, value): - self._endpoints = value - api_args = {'endpoints': self._endpoints} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) - - @property - def endpoint_up_count(self): - """Number of endpoints that must be up for the Record status to be 'up' - """ - return self._endpoint_up_count - @endpoint_up_count.setter - def endpoint_up_count(self, value): - self._endpoint_up_count = value - api_args = {'endpoint_up_count': self._endpoint_up_count} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) - - @property - def eligible(self): - """Indicates whether or not the Record can be served""" - return self._eligible - @eligible.setter - def eligible(self, value): - self._eligible = value - api_args = {'eligible': self._eligible} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) - def to_json(self): """Convert this DSFRecord to a json blob""" - json = {'label': self._label, 'weight': self._weight, - 'automation': self._automation, 'endpoints': self._endpoints, - 'eligible': self._eligible, - 'endpoint_up_count': self._endpoint_up_count} + json = {'label': self.label, 'weight': self.weight, + 'automation': self.automation, 'endpoints': self.endpoints, + 'eligible': self.eligible, + 'endpoint_up_count': self.endpoint_up_count} json_blob = {x: json[x] for x in json if json[x] is not None} - if hasattr(self, '_record_type'): - # label = self._record_type.split('Record')[0].lower() + '_rdata' + + if hasattr(self, 'rdata'): # We don't need to worry about rdata() throwing an error since if # we have a record type, then we know we're a subclass of a # DNSRecord @@ -242,13 +146,14 @@ def to_json(self): def delete(self): """Delete this :class:`DSFRecord`""" api_args = {'publish': 'Y'} - DynectSession.get_session().execute(self.uri, 'DELETE', api_args) + DynectSession.delete(self.uri, api_args) class DSFARecord(_DSFRecord, ARecord): """An :class:`ARecord` object which is able to store additional data for use by a :class:`TrafficDirector` service. """ + def __init__(self, address, ttl=0, label=None, weight=1, automation='auto', endpoints=None, endpoint_up_count=None, eligible=True, **kwargs): @@ -266,8 +171,7 @@ def __init__(self, address, ttl=0, label=None, weight=1, automation='auto', Record status to be 'up' :param eligible: Indicates whether or not the Record can be served """ - ARecord.__init__(self, None, None, address=address, ttl=ttl, - create=False) + ARecord.__init__(self, None, None, address=address, ttl=ttl, api=False) _DSFRecord.__init__(self, label, weight, automation, endpoints, endpoint_up_count, eligible, **kwargs) @@ -276,6 +180,7 @@ class DSFAAAARecord(_DSFRecord, AAAARecord): """An :class:`AAAARecord` object which is able to store additional data for use by a :class:`TrafficDirector` service. """ + def __init__(self, address, ttl=0, label=None, weight=1, automation='auto', endpoints=None, endpoint_up_count=None, eligible=True, **kwargs): @@ -303,6 +208,7 @@ class DSFCERTRecord(_DSFRecord, CERTRecord): """An :class:`CERTRecord` object which is able to store additional data for use by a :class:`TrafficDirector` service. """ + def __init__(self, format, tag, algorithm, certificate, ttl=0, label=None, weight=1, automation='auto', endpoints=None, endpoint_up_count=None, eligible=True, **kwargs): @@ -332,9 +238,10 @@ def __init__(self, format, tag, algorithm, certificate, ttl=0, label=None, class DSFCNAMERecord(_DSFRecord, CNAMERecord): - """An :class:`CNAMERecord` object which is able to store additional data for - use by a :class:`TrafficDirector` service. + """An :class:`CNAMERecord` object which is able to store additional data + for use by a :class:`TrafficDirector` service. """ + def __init__(self, cname, ttl=0, label=None, weight=1, automation='auto', endpoints=None, endpoint_up_count=None, eligible=True, **kwargs): @@ -359,9 +266,10 @@ def __init__(self, cname, ttl=0, label=None, weight=1, automation='auto', class DSFDHCIDRecord(_DSFRecord, DHCIDRecord): - """An :class:`DHCIDRecord` object which is able to store additional data for - use by a :class:`TrafficDirector` service. + """An :class:`DHCIDRecord` object which is able to store additional data + for use by a :class:`TrafficDirector` service. """ + def __init__(self, digest, ttl=0, label=None, weight=1, automation='auto', endpoints=None, endpoint_up_count=None, eligible=True, **kwargs): @@ -386,9 +294,10 @@ def __init__(self, digest, ttl=0, label=None, weight=1, automation='auto', class DSFDNAMERecord(_DSFRecord, DNAMERecord): - """An :class:`DNAMERecord` object which is able to store additional data for - use by a :class:`TrafficDirector` service. + """An :class:`DNAMERecord` object which is able to store additional data + for use by a :class:`TrafficDirector` service. """ + def __init__(self, dname, ttl=0, label=None, weight=1, automation='auto', endpoints=None, endpoint_up_count=None, eligible=True, **kwargs): @@ -416,6 +325,7 @@ class DSFDNSKEYRecord(_DSFRecord, DNSKEYRecord): """An :class:`DNSKEYRecord` object which is able to store additional data for use by a :class:`TrafficDirector` service. """ + def __init__(self, protocol, public_key, algorithm=5, flags=256, ttl=0, label=None, weight=1, automation='auto', endpoints=None, endpoint_up_count=None, eligible=True, **kwargs): @@ -450,6 +360,7 @@ class DSFDSRecord(_DSFRecord, DSRecord): """An :class:`DSRecord` object which is able to store additional data for use by a :class:`TrafficDirector` service. """ + def __init__(self, digest, keytag, algorithm=5, digtype=1, ttl=0, label=None, weight=1, automation='auto', endpoints=None, endpoint_up_count=None, eligible=True, **kwargs): @@ -487,6 +398,7 @@ class DSFKEYRecord(_DSFRecord, KEYRecord): """An :class:`KEYRecord` object which is able to store additional data for use by a :class:`TrafficDirector` service. """ + def __init__(self, algorithm, flags, protocol, public_key, ttl=0, label=None, weight=1, automation='auto', endpoints=None, endpoint_up_count=None, eligible=True, **kwargs): @@ -521,6 +433,7 @@ class DSFKXRecord(_DSFRecord, KXRecord): """An :class:`KXRecord` object which is able to store additional data for use by a :class:`TrafficDirector` service. """ + def __init__(self, exchange, preference, ttl=0, label=None, weight=1, automation='auto', endpoints=None, endpoint_up_count=None, eligible=True, **kwargs): @@ -553,6 +466,7 @@ class DSFLOCRecord(_DSFRecord, LOCRecord): """An :class:`LOCRecord` object which is able to store additional data for use by a :class:`TrafficDirector` service. """ + def __init__(self, altitude, latitude, longitude, horiz_pre=10000, size=1, vert_pre=10, ttl=0, label=None, weight=1, automation='auto', endpoints=None, endpoint_up_count=None, eligible=True, @@ -591,9 +505,11 @@ class DSFIPSECKEYRecord(_DSFRecord, IPSECKEYRecord): """An :class:`IPSECKEYRecord` object which is able to store additional data for use by a :class:`TrafficDirector` service. """ + def __init__(self, precedence, gatetype, algorithm, gateway, public_key, - ttl=0, label=None, weight=1, automation='auto', endpoints=None, - endpoint_up_count=None, eligible=True, **kwargs): + ttl=0, label=None, weight=1, automation='auto', + endpoints=None, endpoint_up_count=None, eligible=True, + **kwargs): """Create a :class:`DSFIPSECKEYRecord` object :param precedence: Indicates priority among multiple IPSECKEYS. Lower @@ -618,8 +534,8 @@ def __init__(self, precedence, gatetype, algorithm, gateway, public_key, """ IPSECKEYRecord.__init__(self, None, None, precedence=precedence, gatetype=gatetype, algorithm=algorithm, - gateway=gateway, public_key=public_key, ttl=ttl, - create=False) + gateway=gateway, public_key=public_key, + ttl=ttl, create=False) _DSFRecord.__init__(self, label, weight, automation, endpoints, endpoint_up_count, eligible, **kwargs) @@ -628,6 +544,7 @@ class DSFMXRecord(_DSFRecord, MXRecord): """An :class:`MXRecord` object which is able to store additional data for use by a :class:`TrafficDirector` service. """ + def __init__(self, exchange, preference=10, ttl=0, label=None, weight=1, automation='auto', endpoints=None, endpoint_up_count=None, eligible=True, **kwargs): @@ -659,6 +576,7 @@ class DSFNAPTRRecord(_DSFRecord, NAPTRRecord): """An :class:`NAPTRRecord` object which is able to store additional data for use by a :class:`TrafficDirector` service. """ + def __init__(self, order, preference, services, regexp, replacement, flags='U', ttl=0, label=None, weight=1, automation='auto', endpoints=None, endpoint_up_count=None, eligible=True, @@ -700,9 +618,10 @@ class DSFPTRRecord(_DSFRecord, PTRRecord): """An :class:`PTRRecord` object which is able to store additional data for use by a :class:`TrafficDirector` service. """ - def __init__(self, ptrdname, ttl=0, label=None, weight=1, automation='auto', - endpoints=None, endpoint_up_count=None, eligible=True, - **kwargs): + + def __init__(self, ptrdname, ttl=0, label=None, weight=1, + automation='auto', endpoints=None, endpoint_up_count=None, + eligible=True, **kwargs): """Create a :class:`DSFPTRRecord` object :param ptrdname: The hostname where the IP address should be directed @@ -727,9 +646,10 @@ class DSFPXRecord(_DSFRecord, PXRecord): """An :class:`PXRecord` object which is able to store additional data for use by a :class:`TrafficDirector` service. """ - def __init__(self, preference, map822, mapx400, ttl=0, label=None, weight=1, - automation='auto', endpoints=None, endpoint_up_count=None, - eligible=True, **kwargs): + + def __init__(self, preference, map822, mapx400, ttl=0, label=None, + weight=1, automation='auto', endpoints=None, + endpoint_up_count=None, eligible=True, **kwargs): """Create a :class:`DSFPXRecord` object :param preference: Sets priority for processing records of the same @@ -748,7 +668,8 @@ def __init__(self, preference, map822, mapx400, ttl=0, label=None, weight=1, :param eligible: Indicates whether or not the Record can be served """ PXRecord.__init__(self, None, None, preference=preference, - map822=map822, mapx400=mapx400, ttl=ttl, create=False) + map822=map822, mapx400=mapx400, ttl=ttl, + create=False) _DSFRecord.__init__(self, label, weight, automation, endpoints, endpoint_up_count, eligible, **kwargs) @@ -757,6 +678,7 @@ class DSFNSAPRecord(_DSFRecord, NSAPRecord): """An :class:`NSAPRecord` object which is able to store additional data for use by a :class:`TrafficDirector` service. """ + def __init__(self, nsap, ttl=0, label=None, weight=1, automation='auto', endpoints=None, endpoint_up_count=None, eligible=True, **kwargs): @@ -783,6 +705,7 @@ class DSFRPRecord(_DSFRecord, RPRecord): """An :class:`RPRecord` object which is able to store additional data for use by a :class:`TrafficDirector` service. """ + def __init__(self, mbox, txtdname, ttl=0, label=None, weight=1, automation='auto', endpoints=None, endpoint_up_count=None, eligible=True, **kwargs): @@ -812,8 +735,9 @@ class DSFNSRecord(_DSFRecord, NSRecord): """An :class:`NSRecord` object which is able to store additional data for use by a :class:`TrafficDirector` service. """ - def __init__(self, nsdname, service_class='', ttl=0, label=None, weight=1, - automation='auto', endpoints=None, endpoint_up_count=None, + + def __init__(self, nsdname, service_class='', ttl=0, label=None, weight=1, + automation='auto', endpoints=None, endpoint_up_count=None, eligible=True, **kwargs): """Create a :class:`DSFNSRecord` object @@ -841,8 +765,9 @@ class DSFSPFRecord(_DSFRecord, SPFRecord): """An :class:`SPFRecord` object which is able to store additional data for use by a :class:`TrafficDirector` service. """ - def __init__(self, txtdata, ttl=0, label=None, weight=1, automation='auto', - endpoints=None, endpoint_up_count=None, eligible=True, + + def __init__(self, txtdata, ttl=0, label=None, weight=1, automation='auto', + endpoints=None, endpoint_up_count=None, eligible=True, **kwargs): """Create a :class:`DSFSPFRecord` object @@ -868,8 +793,9 @@ class DSFSRVRecord(_DSFRecord, SRVRecord): """An :class:`SRVRecord` object which is able to store additional data for use by a :class:`TrafficDirector` service. """ - def __init__(self, port, priority, target, rr_weight, ttl=0, label=None, - weight=1, automation='auto', endpoints=None, + + def __init__(self, port, priority, target, rr_weight, ttl=0, label=None, + weight=1, automation='auto', endpoints=None, endpoint_up_count=None, eligible=True, **kwargs): """Create a :class:`DSFSRVRecord` object @@ -879,9 +805,9 @@ def __init__(self, port, priority, target, rr_weight, ttl=0, label=None, exist on the zone/node :param target: The domain name of a host where the service is running on the specified port - :param rr_weight: Secondary prioritizing of records to serve. Records of - equal priority should be served based on their weight. Higher values - are served more often + :param rr_weight: Secondary prioritizing of records to serve. Records + of equal priority should be served based on their weight. Higher + values are served more often :param ttl: TTL for this record :param label: A unique label for this :class:`DSFSRVRecord` :param weight: Weight for this :class:`DSFSRVRecord` @@ -904,8 +830,9 @@ class DSFTXTRecord(_DSFRecord, TXTRecord): """An :class:`TXTRecord` object which is able to store additional data for use by a :class:`TrafficDirector` service. """ - def __init__(self, txtdata, ttl=0, label=None, weight=1, automation='auto', - endpoints=None, endpoint_up_count=None, eligible=True, + + def __init__(self, txtdata, ttl=0, label=None, weight=1, automation='auto', + endpoints=None, endpoint_up_count=None, eligible=True, **kwargs): """Create a :class:`DSFTXTRecord` object @@ -928,10 +855,45 @@ def __init__(self, txtdata, ttl=0, label=None, weight=1, automation='auto', endpoint_up_count, eligible, **kwargs) -class DSFRecordSet(object): +class DSFRecordSet(DNSAPIObject): """A Collection of DSFRecord Type objects belonging to a :class:`DSFFailoverChain` """ + records = ListAttribute('records') + status = ImmutableAttribute('status') + + #: A unique label for this DSF Record Set + label = StringAttribute('label') + + #: The type of rdata represented by this DFS Record Set + rdata_class = StringAttribute('rdata_class') + + #: Default TTL for all DSF Record's within this DSF Record SEt + ttl = IntegerAttribute('ttl') + + #: Defines how eligibility can be changed in response to monitoring. Must + #: be one of 'auto', 'auto_down', or 'manual' + automation = ValidatedAttribute('automation', + validator=('auto', 'auto_down', 'manual')) + + #: How many records to serve out of this DSF Record Set + serve_count = IntegerAttribute('serve_count') + + #: The number of records that must be failing before this DSF Record Set + #: becomes ineligible + fail_count = IntegerAttribute('fail_count') + + #: The number of Records that must not be okay before this DSFRecordSet + #: becomes in trouble. + trouble_count = IntegerAttribute('trouble_count') + + #: Indicates whether or not this DSFRecordSet can be served. + eligible = BooleanAttribute('eligible') + + #: The unique DynECT system id of the DSF Monitor attached to this + #: DSFRecordSet + dsf_monitor_id = ImmutableAttribute('dsf_monitor_id') + def __init__(self, rdata_class, label=None, ttl=None, automation=None, serve_count=None, fail_count=None, trouble_count=None, eligible=None, dsf_monitor_id=None, records=None, **kwargs): @@ -948,10 +910,10 @@ def __init__(self, rdata_class, label=None, ttl=None, automation=None, :class:`DSFRecordSet` :param fail_count: The number of Records that must not be okay before this :class:`DSFRecordSet` becomes ineligible. - :param trouble_count: The number of Records that must not be okay before - this :class:`DSFRecordSet` becomes in trouble. - :param eligible: Indicates whether or not this :class:`DSFRecordSet` can - be served. + :param trouble_count: The number of Records that must not be okay + before this :class:`DSFRecordSet` becomes in trouble. + :param eligible: Indicates whether or not this :class:`DSFRecordSet` + can be served. :param dsf_monitor_id: The unique system id of the DSF Monitor attached to this :class:`DSFRecordSet` :param records: A list of :class:`DSFRecord`'s within this @@ -959,16 +921,15 @@ def __init__(self, rdata_class, label=None, ttl=None, automation=None, :param kwargs: Used for manipulating additional data to be speicified by the creation of other system objects. """ - super(DSFRecordSet, self).__init__() - self._label = label self._rdata_class = rdata_class - self._ttl = ttl - self._automation = automation - self._serve_count = serve_count - self._fail_count = fail_count - self._trouble_count = trouble_count - self._eligible = eligible - self._dsf_monitor_id = dsf_monitor_id + super(DSFRecordSet, self).__init__(rdata_class, label=label, ttl=ttl, + automation=automation, + serve_count=serve_count, + fail_count=fail_count, + trouble_count=trouble_count, + eligible=eligible, + dsf_monitor_id=dsf_monitor_id, + records=records, **kwargs) if records is not None and len(records) > 0 and isinstance(records[0], dict): self._records = [] @@ -988,7 +949,7 @@ def __init__(self, rdata_class, label=None, ttl=None, automation=None, 'srv': DSFSRVRecord, 'txt': DSFTXTRecord} rec_type = record['rdata_class'].lower() constructor = constructors[rec_type] - rdata_key = 'rdata_{}'.format(rec_type) + rdata_key = 'rdata_{0}'.format(rec_type) kws = ('ttl', 'label', 'weight', 'automation', 'endpoints', 'endpoint_up_count', 'eligible', 'dsf_record_id', 'dsf_record_set_id', 'status', 'torpidity') @@ -1007,9 +968,10 @@ def __init__(self, rdata_class, label=None, ttl=None, automation=None, if key != 'records': setattr(self, '_' + key, val) # If dsf_id and dsf_response_pool_id were specified in kwargs - if self._service_id is not None and self._dsf_record_set_id is not None: - self.uri = '/DSFRecordSet/{}/{}/'.format(self._service_id, - self._dsf_record_set_id) + if self._service_id is not None \ + and self._dsf_record_set_id is not None: + self.uri = '/DSFRecordSet/{0}/{1}/'.format(self._service_id, + self._dsf_record_set_id) def _post(self, dsf_id): """Create a new :class:`DSFRecordSet` on the DynECT System @@ -1018,7 +980,7 @@ def _post(self, dsf_id): :class:`DSFRecordSet` is attached to """ self._service_id = dsf_id - uri = '/DSFRecordSet/{}/'.format(self._service_id) + uri = '/DSFRecordSet/{0}/'.format(self._service_id) api_args = {} for key, val in self.__dict__.items(): if key == 'records': @@ -1026,10 +988,10 @@ def _post(self, dsf_id): elif val is not None and not hasattr(val, '__call__') and \ key.startswith('_'): api_args[key[1:]] = val - response = DynectSession.get_session().execute(uri, 'POST', api_args) + response = DynectSession.post(uri, api_args) self._build(response['data']) - self.uri = '/DSFRecordSet/{}/{}/'.format(self._service_id, - self._dsf_record_set_id) + self.uri = '/DSFRecordSet/{0}/{1}/'.format(self._service_id, + self._dsf_record_set_id) def _get(self, dsf_id, dsf_record_set_id): """Get an existing :class:`DSFRecordSet` from the DynECT System @@ -1041,11 +1003,9 @@ def _get(self, dsf_id, dsf_record_set_id): """ self._service_id = dsf_id self._dsf_record_set_id = dsf_record_set_id - self.uri = '/DSFRecordSet/{}/{}/'.format(self._service_id, - self._dsf_record_set_id) - api_args = {} - response = DynectSession.get_session().execute(self.uri, 'GET', - api_args) + self.uri = '/DSFRecordSet/{0}/{1}/'.format(self._service_id, + self._dsf_record_set_id) + response = DynectSession.get(self.uri) self._build(response['data']) def _build(self, data): @@ -1054,197 +1014,49 @@ def _build(self, data): if key != 'records': setattr(self, '_' + key, val) - @property - def records(self): - """The list of DSFRecord types that are stored in this - :class:`DSFRecordSet` - """ - return self._records - @records.setter - def records(self, value): - pass - - @property - def status(self): - """The current status of this :class:`DSFRecordSet`""" - self._get(self._service_id, self._dsf_record_set_id) - return self._status - @status.setter - def status(self, value): - pass + def to_json(self): + """Convert this :class:`DSFRecordSet` to a JSON blob""" + json_blob = {'rdata_class': self._rdata_class, + 'label': self.label, 'ttl': self.ttl, + 'automation': self.automation, + 'serve_count': self.serve_count, + 'fail_count': self.fail_count, + 'trouble_count': self.trouble_count, + 'eligible': self.eligible, + 'dsf_monitor_id': self.dsf_monitor_id, + 'records': [rec.to_json() for rec in self.records]} + return {key: json_blob[key] for key in json_blob if json_blob[key]} - @property - def label(self): - """A unique label for this :class:`DSFRecordSet`""" - return self._label - @label.setter - def label(self, value): - self._label = value - api_args = {'label': self._label} - if self._master_line: - api_args['master_line'] = self._master_line - else: - api_args['rdata'] = self._rdata - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) + def delete(self): + """Delete this :class:`DSFRecordSet` from the Dynect System""" + api_args = {'publish': 'Y'} + DynectSession.delete(self.uri, api_args) - @property - def rdata_class(self): - """The rdata property is a read-only attribute""" - return self._rdata_class - @rdata_class.setter - def rdata_class(self, value): - pass + def __str__(self): + return ': {1}'.format(self.rdata_class, self.label) - @property - def ttl(self): - """Default TTL for :class:`DSFRecord`'s within this - :class:`DSFRecordSet`""" - return self._ttl - @ttl.setter - def ttl(self, value): - self._ttl = value - api_args = {'ttl': self._ttl} - if self._master_line: - api_args['master_line'] = self._master_line - else: - api_args['rdata'] = self._rdata - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) - @property - def automation(self): - """Defines how eligible can be changed in response to monitoring""" - return self._automation - @automation.setter - def automation(self, value): - self._automation = value - api_args = {'automation': self._automation} - if self._master_line: - api_args['master_line'] = self._master_line - else: - api_args['rdata'] = self._rdata - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) +class DSFFailoverChain(DNSAPIObject): + uri = '/DSFRecordSetFailoverChain/{0}/{1}/' - @property - def serve_count(self): - """How many Records to serve out of this :class:`DSFRecordSet`""" - return self._serve_count - @serve_count.setter - def serve_count(self, value): - self._serve_count = value - api_args = {'serve_count': self._serve_count} - if self._master_line: - api_args['master_line'] = self._master_line - else: - api_args['rdata'] = self._rdata - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) + #: A unique label for this DSF Failover Chain + label = StringAttribute('label') - @property - def fail_count(self): - """The number of Records that must not be okay before this - :class:`DSFRecordSet` becomes ineligible. - """ - return self._fail_count - @fail_count.setter - def fail_count(self, value): - self._fail_count = value - api_args = {'fail_count': self._fail_count} - if self._master_line: - api_args['master_line'] = self._master_line - else: - api_args['rdata'] = self._rdata - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) + #: Indicates whether or not the contained DSF Record Sets are core record + #: sets + core = BooleanAttribute('core') - @property - def trouble_count(self): - """The number of Records that must not be okay before this - :class:`DSFRecordSet` becomes in trouble. - """ - return self._trouble_count - @trouble_count.setter - def trouble_count(self, value): - self._trouble_count = value - api_args = {'trouble_count': self._trouble_count} - if self._master_line: - api_args['master_line'] = self._master_line - else: - api_args['rdata'] = self._rdata - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) + #: A :const:`list` of :class:`DSFRecordSet`'s for this DSF Failover Chain + record_sets = ListAttribute('record_sets') - @property - def eligible(self): - """Indicates whether or not this :class:`DSFRecordSet` can be served.""" - return self._eligible - @eligible.setter - def eligible(self, value): - self._eligible = value - api_args = {'eligible': self._eligible} - if self._master_line: - api_args['master_line'] = self._master_line - else: - api_args['rdata'] = self._rdata - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) + #: The unique DynECT system id for the response pool that this DSF Failover + #: Chain belongs to + dsf_response_pool_id = ImmutableAttribute('dsf_response_pool_id') - @property - def dsf_monitor_id(self): - """The unique system id of the DSF Monitor attached to this - :class:`DSFRecordSet` - """ - return self._dsf_monitor_id - @dsf_monitor_id.setter - def dsf_monitor_id(self, value): - self._dsf_monitor_id = value - api_args = {'dsf_monitor_id': self._dsf_monitor_id} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) + #: The unique system id for the Traffic Director service that this failover + #: chain belongs to + dsf_id = ImmutableAttribute('dsf_id') - def to_json(self): - """Convert this :class:`DSFRecordSet` to a JSON blob""" - json_blob = {'rdata_class': self._rdata_class} - if self._label: - json_blob['label'] = self._label - if self._ttl: - json_blob['ttl'] = self._ttl - if self._automation: - json_blob['automation'] = self._automation - if self._serve_count: - json_blob['serve_count'] = self._serve_count - if self._fail_count: - json_blob['fail_count'] = self._fail_count - if self._trouble_count: - json_blob['trouble_count'] = self._trouble_count - if self._eligible: - json_blob['eligible'] = self._eligible - if self._dsf_monitor_id: - json_blob['dsf_monitor_id'] = self._dsf_monitor_id - if self._records: - json_blob['records'] = [rec.to_json() for rec in self._records] - else: - json_blob['records'] = [] - return json_blob - - def delete(self): - """Delete this :class:`DSFRecordSet` from the Dynect System""" - api_args = {'publish': 'Y'} - DynectSession.get_session().execute(self.uri, 'DELETE', api_args) - - -class DSFFailoverChain(object): - """docstring for DSFFailoverChain""" def __init__(self, label=None, core=None, record_sets=None, **kwargs): """Create a :class:`DSFFailoverChain` object @@ -1254,7 +1066,6 @@ def __init__(self, label=None, core=None, record_sets=None, **kwargs): :param record_sets: A list of :class:`DSFRecordSet`'s for this :class:`DSFFailoverChain` """ - super(DSFFailoverChain, self).__init__() self._label = label self._core = core if isinstance(record_sets, list) and len(record_sets) > 0 and \ @@ -1263,20 +1074,13 @@ def __init__(self, label=None, core=None, record_sets=None, **kwargs): self._record_sets = [] # Create new record set objects for record_set in record_sets: - if 'service_id' in record_set and \ - record_set['service_id'] == '': + if 'service_id' in record_set \ + and record_set['service_id'] == '': record_set['service_id'] = kwargs['service_id'] - self._record_sets.append(DSFRecordSet(**record_set)) + self._record_sets.append(DSFRecordSet(api=False, **record_set)) else: self._record_sets = record_sets - self._dsf_id = self._dsf_response_pool_id = self.uri = None - for key, val in kwargs.items(): - setattr(self, '_' + key, val) - # If dsf_id and dsf_response_pool_id were specified in kwargs - if self._dsf_id is not None and self._dsf_response_pool_id is not None: - r_pid = self._dsf_response_pool_id - self.uri = '/DSFRecordSetFailoverChain/{}/{}/'.format(self._dsf_id, - r_pid) + super(DSFFailoverChain, self).__init__(**kwargs) def _post(self, dsf_id, dsf_response_pool_id): """Create a new :class:`DSFFailoverChain` on the Dynect System @@ -1288,8 +1092,7 @@ def _post(self, dsf_id, dsf_response_pool_id): """ self._dsf_id = dsf_id self._dsf_response_pool_id = dsf_response_pool_id - self.uri = '/DSFRecordSetFailoverChain/{}/{}/'.format(self._dsf_id, - self._dsf_response_pool_id) + self.uri = self.uri.format(self.dsf_id, self.dsf_response_pool_id) api_args = {'publish': 'Y'} if self._label: api_args['label'] = self._label @@ -1297,14 +1100,12 @@ def _post(self, dsf_id, dsf_response_pool_id): api_args['core'] = self._core if self._record_sets: api_args['record_sets'] = self._record_sets.to_json() - response = DynectSession.get_session().execute(self.uri, 'POST', - api_args) - for key, val in response['data'].items(): - if key != 'record_sets': - setattr(self, '_' + key, val) + resp = DynectSession.post(self.uri, api_args) + self._build(resp['data']) def _get(self, dsf_id, dsf_response_pool_id): - """Retrieve an existing :class:`DSFFailoverChain` from the Dynect System + """Retrieve an existing :class:`DSFFailoverChain` from the Dynect + System :param dsf_id: The unique system id of the DSF service this :class:`DSFFailoverChain` is attached to @@ -1313,75 +1114,72 @@ def _get(self, dsf_id, dsf_response_pool_id): """ self._dsf_id = dsf_id self._dsf_response_pool_id = dsf_response_pool_id - self.uri = '/DSFRecordSetFailoverChain/{}/{}/'.format(self._dsf_id, - self._dsf_response_pool_id) - api_args = {} - response = DynectSession.get_session().execute(self.uri, 'GET', - api_args) - for key, val in response['data'].items(): - if key != 'record_sets': - setattr(self, '_' + key, val) + self.uri = self.uri.format(self.dsf_id, self.dsf_response_pool_id) + super(DSFFailoverChain, self)._get() - @property - def label(self): - """A unique label for this :class:`DSFFailoverChain`""" - return self._label - @label.setter - def label(self, value): - self._label = value - api_args = {'label': self._label} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - for key, val in response['data'].items(): - if key != 'record_sets': - setattr(self, '_' + key, val) - - @property - def core(self): - """Indicates whether or not the contained :class:`DSFRecordSet`'s are - core record sets. - """ - return self._core - @core.setter - def core(self, value): - self._core = value - api_args = {'core': self._core} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - for key, val in response['data'].items(): - if key != 'record_sets': - setattr(self, '_' + key, val) - - @property - def record_sets(self): - """A list of hashes to create a new :class:`DSFRecordSet` or - specify/update an existing :class:`DSFRecordSet` - """ - return self._record_sets - @record_sets.setter - def record_sets(self, value): - pass + def _build(self, data): + if 'record_sets' in data: + data.pop('record_sets') + super(DSFFailoverChain, self)._build(data) def to_json(self): - """Convert this :class:`DSFFailoverChain` to a JSON blob""" + """Convert this :class:`DSFFailoverChain` to JSON""" json_blob = {} if self._label: json_blob['label'] = self._label if self._core: json_blob['core'] = self._core if self.record_sets: - json_blob['record_sets'] = [rs.to_json() for rs in self.record_sets] + json_blob['record_sets'] = [rs.to_json() for rs in + self.record_sets] return json_blob def delete(self): """Delete this :class:`DSFFailoverChain` from the Dynect System""" api_args = {'publish': 'Y'} - DynectSession.get_session().execute(self.uri, 'DELETE', api_args) + DynectSession.delete(self.uri, api_args) + + def __str__(self): + return ': {0}'.format(self.label) -class DSFResponsePool(object): - """docstring for DSFResponsePool""" - def __init__(self, label, core_set_count=1, eligible=True, +class DSFResponsePool(DNSAPIObject): + uri = '/DSFResponsePool/{0}/{1}/' + _get_length = 1 + + #: A unique label for this DSF Response Pool + label = StringAttribute('label') + + #: If fewer than this number of core record sets are eligible, status will + #: be set to 'fail' + core_set_count = IntegerAttribute('core_set_count') + + #: Indicates whether or not this DSF Response Pool can be served + eligible = BooleanAttribute('eligible') + + #: Defines how eligibility can be changed in response to monitoring + automation = StringAttribute('automation') + + #: The unique DynECT system id for the :class:`DSFRuleset` that this + #: response pool belongs to + dsf_ruleset_id = ImmutableAttribute('dsf_ruleset_id') + + #: The unique DynECT system id for this DSF Response Pool + dsf_response_pool_id = ImmutableAttribute('dsf_response_pool_id') + + #: When specified with `dsf_ruleset_id`, indicates the position of the + #: DSF Response Pool + index = IntegerAttribute('index') + + #: A :const:`list` of :class:`DSFFailoverChain`'s that are defined for this + #: DSF Response Pool + rs_chains = ImmutableAttribute('rs_chains') + + #: The unique DynECT system id for the Traffic Director service that this + #: DFS Response Pool belongs to + dsf_id = ImmutableAttribute('dsf_id') + + def __init__(self, label, dsf_id=None, core_set_count=1, eligible=True, automation='auto', dsf_ruleset_id=None, index=None, rs_chains=None, **kwargs): """Create a :class:`DSFResponsePool` object @@ -1400,178 +1198,91 @@ def __init__(self, label, core_set_count=1, eligible=True, :param rs_chains: A list of :class:`DSFFailoverChain` that are defined for this :class:`DSFResponsePool` """ - super(DSFResponsePool, self).__init__() - self._label = label - self._core_set_count = core_set_count - self._eligible = eligible - self._automation = automation - self._dsf_ruleset_id = dsf_ruleset_id - self._index = index + self._label, self._dsf_id = label, dsf_id + if dsf_id is None: + kwargs['api'] = False + if isinstance(rs_chains, list) and len(rs_chains) > 0 and \ isinstance(rs_chains[0], dict): # Clear Failover Chains self._rs_chains = [] # Create a new FO Chain for each entry returned from API for chain in rs_chains: - self._rs_chains.append(DSFFailoverChain(**chain)) + self._rs_chains.append(DSFFailoverChain(api=False, **chain)) else: self._rs_chains = rs_chains - self._dsf_id = self._dsf_response_pool_id = self.uri = None - for key, val in kwargs.items(): - setattr(self, '_' + key, val) - # If dsf_id and dsf_response_pool_id were specified in kwargs - if self._dsf_id is not None and self._dsf_response_pool_id is not None: - r_pid = self._dsf_response_pool_id - self.uri = '/DSFResponsePool/{}/{}/'.format(self._dsf_id, - r_pid) - def _post(self, dsf_id): - """Create a new :class:`DSFResponsePool` on the DynECT System + super(DSFResponsePool, self).__init__(core_set_count=core_set_count, + eligible=eligible, + automation=automation, + dsf_ruleset_id=dsf_ruleset_id, + index=index, **kwargs) - :param dsf_id: the id of the DSF service this :class:`DSFResponsePool` - is attached to - """ - self._dsf_id = dsf_id - uri = '/DSFResponsePool/{}/'.format(self._dsf_id) + def _post(self): + """Create a new :class:`DSFResponsePool` on the DynECT System""" api_args = {'publish': 'Y', 'label': self._label, - 'core_set_count': self._core_set_count, - 'eligible': self._eligible, 'automation': self._automation} - if self._dsf_ruleset_id: - api_args['dsf_ruleset_id'] = self._dsf_ruleset_id - if self._index: - api_args['index'] = self._index + 'core_set_count': self.core_set_count, + 'eligible': self.eligible, 'automation': self.automation} + if self.dsf_ruleset_id: + api_args['dsf_ruleset_id'] = self.dsf_ruleset_id + if self.index: + api_args['index'] = self.index if self._rs_chains: api_args['rs_chains'] = self._rs_chains - response = DynectSession.get_session().execute(uri, 'POST', api_args) - for key, val in response['data'].items(): - if key != 'rs_chains': - setattr(self, '_' + key, val) - self.uri = '/DSFResponsePool/{}/{}/'.format(self._dsf_id, - self._dsf_response_pool_id) + resp = DynectSession.post(self.uri, api_args) + self._build(resp['data']) - def _get(self, dsf_id, dsf_response_pool_id): + def _get(self, dsf_response_pool_id): """Get an existing :class:`DSFResponsePool` from the DynECT System - :param dsf_id: the id of the DSF service this :class:`DSFResponsePool` - is attached to :param dsf_response_pool_id: the id of this :class:`DSFResponsePool` """ - self._dsf_id = dsf_id - self._dsf_response_pool_id = dsf_response_pool_id - self.uri = '/DSFResponsePool/{}/{}/'.format(self._dsf_id, - self._dsf_response_pool_id) - api_args = {} - response = DynectSession.get_session().execute(self.uri, 'GET', - api_args) - for key, val in response['data'].items(): - if key != 'rs_chains': - setattr(self, '_' + key, val) - - @property - def label(self): - """A unique label for this :class:`DSFResponsePool`""" - return self._label - @label.setter - def label(self, value): - self._label = value - api_args = {'label': self._label} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - for key, val in response['data'].items(): - if key != 'record_sets': - setattr(self, '_' + key, val) - - @property - def core_set_count(self): - """If fewer than this number of core record sets are eligible, status - will be set to fail - """ - return self._core_set_count - @core_set_count.setter - def core_set_count(self, value): - self._core_set_count = value - api_args = {'core_set_count': self._core_set_count} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - for key, val in response['data'].items(): - if key != 'record_sets': - setattr(self, '_' + key, val) - - @property - def eligible(self): - """Indicates whether or not the :class:`DSFResponsePool` can be served - """ - return self._eligible - @eligible.setter - def eligible(self, value): - self._eligible = value - api_args = {'eligible': self._eligible} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - for key, val in response['data'].items(): - if key != 'record_sets': - setattr(self, '_' + key, val) - - @property - def automation(self): - """Defines how eligible can be changed in response to monitoring""" - return self._automation - @automation.setter - def automation(self, value): - self._automation = value - api_args = {'automation': self._automation} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - for key, val in response['data'].items(): - if key != 'record_sets': - setattr(self, '_' + key, val) - - @property - def dsf_ruleset_id(self): - """Unique system id of the Ruleset this :class:`DSFResponsePool` is - attached to - """ - return self._dsf_ruleset_id - @dsf_ruleset_id.setter - def dsf_ruleset_id(self, value): - self._dsf_ruleset_id = value - api_args = {'dsf_ruleset_id': self._dsf_ruleset_id} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - for key, val in response['data'].items(): - if key != 'record_sets': - setattr(self, '_' + key, val) + self.uri = self.uri.format(self.dsf_id, dsf_response_pool_id) + response = DynectSession.get(self.uri) + self._build(response['data']) - @property - def rs_chains(self): - """A list of :class:`DSFFailoverChain` that are defined for this - :class:`DSFResponsePool` - """ - return self._rs_chains - @rs_chains.setter - def rs_chains(self, value): - pass + def _build(self, data): + if 'rs_chains' in data: + data.pop('rs_chains') + super(DSFResponsePool, self)._build(data) + self.uri = self.uri.format(self.dsf_id, self.dsf_response_pool_id) def to_json(self): """Convert this :class:`DSFResponsePool` to a JSON blob""" rs_json = [rs.to_json() for rs in self._rs_chains] - json_blob = {'label': self._label, 'eligible': self._eligible, - 'core_set_count': self._core_set_count, - 'automation': self._automation, 'rs_chains': rs_json} - if self._index: - json_blob['index'] = self._index - if self._dsf_ruleset_id: - json_blob['dsf_ruleset_id'] = self._dsf_ruleset_id + json_blob = {'label': self.label, 'eligible': self.eligible, + 'core_set_count': self.core_set_count, + 'automation': self.automation, 'rs_chains': rs_json} + if self.index: + json_blob['index'] = self.index + if self.dsf_ruleset_id: + json_blob['dsf_ruleset_id'] = self.dsf_ruleset_id return json_blob def delete(self): """Delete this :class:`DSFResponsePool` from the DynECT System""" api_args = {'publish': 'Y'} - DynectSession.get_session().execute(self.uri, 'DELETE', api_args) + DynectSession.delete(self.uri, api_args) + + def __str__(self): + return ': {0}'.format(self.dsf_response_pool_id) + + +class DSFRuleset(DNSAPIObject): + #: A unique label for this DSF Ruleset + label = StringAttribute('label') + + #: A set of rules describing what traffic is applied to this DSF Ruleset. + #: Must be one of 'always' or 'geoip' + criteria_type = ValidatedAttribute('criteria_type', + validator=('always', 'geoip')) + + #: Varied depending on this specified `criteria_type` + criteria = ListAttribute('criteria') + #: A :const:`list` of :class:`DSFResponsePool`'s for this DSF Ruleset + response_pools = ImmutableAttribute('response_pools') -class DSFRuleset(object): - """docstring for DSFRuleset""" def __init__(self, label, criteria_type, response_pools, criteria=None, **kwargs): """Create a :class:`DSFRuleset` object @@ -1583,28 +1294,19 @@ def __init__(self, label, criteria_type, response_pools, criteria=None, :param response_pools: A list of :class:`DSFResponsePool`'s for this :class:`DSFRuleset` """ - super(DSFRuleset, self).__init__() - self.valid_criteria_types = ('always', 'geoip') - self.valid_criteria = {'always': (), - 'geoip': ()} - self._label = label - self._criteria_type = criteria_type + self._label, self._criteria_type = label, criteria_type self._criteria = criteria if isinstance(response_pools, list) and len(response_pools) > 0 and \ isinstance(response_pools[0], dict): self._response_pools = [] for pool in response_pools: pool = {x: pool[x] for x in pool if x != 'rulesets'} - self._response_pools.append(DSFResponsePool(**pool)) + self._response_pools.append(DSFResponsePool(api=False, **pool)) + kwargs['api'] = False else: self._response_pools = response_pools self._service_id = self._dsf_ruleset_id = self.uri = None - for key, val in kwargs.items(): - setattr(self, '_' + key, val) - # If dsf_id and dsf_ruleset_id were specified in kwargs - if self._service_id is not None and self._dsf_ruleset_id is not None: - self.uri = '/DSFRuleset/{}/{}/'.format(self._service_id, - self._dsf_ruleset_id) + super(DSFRuleset, self).__init__(**kwargs) def _post(self, dsf_id): """Create a new :class:`DSFRuleset` on the DynECT System @@ -1613,16 +1315,14 @@ def _post(self, dsf_id): attached to """ self._service_id = dsf_id - uri = '/DSFRuleset/{}/'.format(self._service_id) + uri = '/DSFRuleset/{0}/'.format(self._service_id) api_args = {'publish': 'Y', 'label': self._label, 'criteria_type': self._criteria_type, 'criteria': self._criteria} - response = DynectSession.get_session().execute(uri, 'POST', api_args) - for key, val in response['data'].items(): - if key != 'rs_chains': - setattr(self, '_' + key, val) - self.uri = '/DSFRuleset/{}/{}/'.format(self._service_id, - self._dsf_ruleset_id) + response = DynectSession.post(uri, api_args) + self._build(response['data']) + self.uri = '/DSFRuleset/{0}/{1}/'.format(self._service_id, + self._dsf_ruleset_id) def _get(self, dsf_id, dsf_ruleset_id): """Get an existing :class:`DSFRuleset` from the DynECT System @@ -1631,76 +1331,20 @@ def _get(self, dsf_id, dsf_ruleset_id): attached to :param dsf_ruleset_id: The unique system id of this :class:`DSFRuleset` """ - self._service_id = dsf_id - self._dsf_ruleset_id = dsf_ruleset_id - self.uri = '/DSFRuleset/{}/{}/'.format(self._service_id, - self._dsf_ruleset_id) - api_args = {} - response = DynectSession.get_session().execute(self.uri, 'GET', - api_args) - for key, val in response['data'].items(): - if key != 'rs_chains': - setattr(self, '_' + key, val) + self.uri = '/DSFRuleset/{0}/{1}/'.format(dsf_id, dsf_ruleset_id) + super(DSFRuleset, self)._get() - @property - def label(self): - """A unique label for this :class:`DSFRuleset`""" - return self._label - @label.setter - def label(self, value): - self._label = value - api_args = {'label': self._label} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - for key, val in response['data'].items(): - if key != 'record_sets': - setattr(self, '_' + key, val) - - @property - def criteria_type(self): - """A set of rules describing what traffic is applied to the - :class:`DSFRuleset` - """ - return self._criteria_type - @criteria_type.setter - def criteria_type(self, value): - self._criteria_type = value - api_args = {'criteria_type': self._criteria_type} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - for key, val in response['data'].items(): - if key != 'record_sets': - setattr(self, '_' + key, val) - - @property - def criteria(self): - """The criteria rules, will be varied depending on the specified - criteria_type - """ - return self._criteria - @criteria.setter - def criteria(self, value): - self._criteria = value - api_args = {'criteria': self._criteria} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - for key, val in response['data'].items(): - if key != 'record_sets': - setattr(self, '_' + key, val) - - @property - def response_pools(self): - """A list of :class:`DSFResponsePool`'s for this :class:`DSFRuleset`""" - return self._response_pools - @response_pools.setter - def response_pools(self, value): - pass + def _build(self, data): + """Pop out unused rs_chains data""" + if 'rs_chains' in data: + data.pop('rs_chains') + super(DSFRuleset, self)._build(data) - @property - def _json(self): + def to_json(self): """JSON-ified version of this DSFRuleset Object""" pool_json = [pool.to_json() for pool in self._response_pools] - json_blob = {'label': self._label, 'criteria_type': self._criteria_type, + json_blob = {'label': self._label, + 'criteria_type': self._criteria_type, 'criteria': self._criteria, 'response_pools': pool_json} return json_blob @@ -1710,18 +1354,23 @@ def delete(self): :class:`TrafficDirector` Service """ api_args = {'publish': 'Y'} - DynectSession.get_session().execute(self.uri, 'DELETE', api_args) + DynectSession.delete(self.uri, api_args) + + def __str__(self): + return ': {0}'.format(self.label) class DSFMonitorEndpoint(object): """An Endpoint object to be passed to a :class:`DSFMonitor`""" + def __init__(self, address, label, active='Y', site_prefs=None): """Create a :class:`DSFMonitorEndpoint` object :param address: The address to monitor. :param label: A label to identify this :class:`DSFMonitorEndpoint`. - :param active: Indicates whether or not this :class:`DSFMonitorEndpoint` - endpoint is active. Must be one of True, False, 'Y', or 'N' + :param active: Indicates whether or not this + :class:`DSFMonitorEndpoint` endpoint is active. Must be one of + True, False, 'Y', or 'N' :param site_prefs: A ``list`` of site codes from which this :class:`DSFMonitorEndpoint` will be monitored """ @@ -1743,12 +1392,10 @@ def _update(self, api_args): if id(endpoint) == id(self): args_list.append(api_args) else: - args_list.append(endpoint._json) - api_args = {'endpoints': args_list} - self._monitor._update(api_args) + args_list.append(endpoint.to_json()) + self._monitor._update(endpoints=args_list) - @property - def _json(self): + def to_json(self): """Get the JSON representation of this :class:`DSFMonitorEndpoint` object """ @@ -1767,137 +1414,157 @@ def active(self): this :class:`DSFMonitorEndpoint` """ return self._active + @active.setter def active(self, value): valid_input = ('Y', 'N', True, False) if value not in valid_input: raise DynectInvalidArgumentError('active', value, valid_input) - api_args = self._json + api_args = self.to_json() api_args['active'] = value self._update(api_args) @property def label(self): return self._label + @label.setter def label(self, value): - api_args = self._json + api_args = self.to_json() api_args['label'] = value self._update(api_args) @property def address(self): return self._address + @address.setter def address(self, value): - api_args = self._json + api_args = self.to_json() api_args['address'] = value self._update(api_args) @property def site_prefs(self): return self._site_prefs + @site_prefs.setter def site_prefs(self, value): - api_args = self._json + api_args = self.to_json() api_args['site_prefs'] = value self._update(api_args) -class DSFMonitor(object): +class DSFMonitor(DNSAPIObject): """A Monitor for a :class:`TrafficDirector` Service""" - def __init__(self, *args, **kwargs): - """Create a new :class:`DSFMonitor` object - - :param label: A unique label to identify this :class:`DSFMonitor` - :param protocol: The protocol to monitor. Must be one of 'HTTP', - 'HTTPS', 'PING', 'SMTP', or 'TCP' - :param response_count: The number of responses to determine whether or - not the endpoint is 'up' or 'down' - :param probe_interval: How often to run this :class:`DSFMonitor` - :param retries: How many retries this :class:`DSFMonitor` should attempt - on failure before giving up. - :param active: Indicates if this :class:`DSFMonitor` is active - :param options: Additional options pertaining to this - :class:`DSFMonitor` - :param endpoints: A List of :class:`DSFMonitorEndpoint`'s that are - associated with this :class:`DSFMonitor` - """ - super(DSFMonitor, self).__init__() - self.uri = None - self._monitor_id = None - self._label = self._protocol = self._response_count = None - self._probe_interval = self._retries = self._active = None - self._options = self._dsf_monitor_id = self._timeout = self._port = None - self._path = self._host = self._header = self._expected = None - self._endpoints = [] - if 'api' in kwargs: - del kwargs['api'] - for key, val in kwargs.items(): - setattr(self, '_' + key, val) - self.uri = '/DSFMonitor/{}/'.format(self._dsf_monitor_id) - elif len(args) + len(kwargs) == 1: - self._get(*args, **kwargs) + uri = '/DSFMonitor/' + _get_length = 1 + + #: The unique DynECT system id for this DSF Monitor + dsf_monitor_id = ImmutableAttribute('dsf_monitor_id') + + #: A unique label used to describe this DSF Monitor + label = StringAttribute('label') + + #: The protocol for this monitor to use while monitoring. Must be one of + #: 'HTTP', 'HTTPS', 'PING', 'SMTP', or 'TCP' + protocol = ValidatedAttribute('protocol', + validator=('HTTP', 'HTTPS', 'PING', 'SMTP', + 'TCP')) + + #: The number of responses to determine whether or not the endpoint is + #: 'up' or 'down' + response_count = IntegerAttribute('response_count') + + #: How often to this DSF Monitor should monitor + probe_interval = IntegerAttribute('probe_interval') + + #: How many retries this :class:`DSFMonitor` should attempt on failure + #: before giving up. + retries = IntegerAttribute('retries') + + #: Indicates whether or not this DSFMonitor is active + active = StringAttribute('active') + + #: Additional options pertaining to this DSF Monitor + options = APIDescriptor('options') + + #: A List of :class:`DSFMonitorEndpoint`'s that are associated with this + #: DSFMonitor + endpoints = ListAttribute('endpoints') + + def __init__(self, label=None, monitor_id=None, protocol=None, + response_count=None, probe_interval=None, retries=None, + active='Y', timeout=None, port=None, path=None, host=None, + header=None, expected=None, endpoints=None): + """ + + :param label: + :param monitor_id: + :param protocol: + :param response_count: + :param probe_interval: + :param retries: + :param active: + :param timeout: + :param port: + :param path: + :param host: + :param header: + :param expected: + :param endpoints: + :return: + """ + if monitor_id is not None: + super(DSFMonitor, self).__init__(monitor_id=monitor_id) else: - self._post(*args, **kwargs) + self._monitor_id = None + super(DSFMonitor, self).__init__(label=label, protocol=protocol, + response_count=response_count, + probe_interval=probe_interval, + retries=retries, active=active, + timeout=timeout, port=port, + path=path, + host=host, header=header, + expected=expected, + endpoints=endpoints) def _get(self, monitor_id): """Get an existing :class:`DSFMonitor` from the DynECT System""" - self._monitor_id = monitor_id - self.uri = '/DSFMonitor/{}/'.format(self._monitor_id) - api_args = {} - response = DynectSession.get_session().execute(self.uri, 'GET', - api_args) - self._build(response['data']) + self.uri = '/DSFMonitor/{0}/'.format(monitor_id) + super(DSFMonitor, self)._get() def _post(self, label, protocol, response_count, probe_interval, retries, active='Y', timeout=None, port=None, path=None, host=None, header=None, expected=None, endpoints=None): """Create a new :class:`DSFMonitor` on the DynECT System""" - uri = '/DSFMonitor/' - self._label = label - self._protocol = protocol - self._response_count = response_count - self._probe_interval = probe_interval - self._retries = retries self._active = Active(active) self._options = {} if timeout: - self._timeout = timeout self._options['timeout'] = timeout if port: - self._port = port self._options['port'] = port if path: - self._path = path self._options['path'] = path if host: - self._host = host self._options['host'] = host if header: - self._header = header self._options['header'] = header if expected: - self._expected = expected self._options['expected'] = expected - self._endpoints = endpoints - api_args = {'label': self._label, - 'protocol': self._protocol, - 'response_count': self._response_count, - 'probe_interval': self._probe_interval, - 'retries': self._retries, - 'active': str(self._active), + api_args = {'label': label, 'protocol': protocol, + 'response_count': response_count, + 'probe_interval': probe_interval, + 'retries': retries, 'active': str(self._active), 'options': self._options} - if self._endpoints is not None: - api_args['endpoints'] = [x._json for x in self._endpoints] - response = DynectSession.get_session().execute(uri, 'POST', api_args) - self._build(response['data']) - self.uri = '/DSFMonitor/{}/'.format(self._dsf_monitor_id) - def _update(self, api_args): - """Private Update method""" - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) + if self.endpoints is None and endpoints is not None: + self._endpoints = endpoints + + if self.endpoints is not None: + api_args['endpoints'] = [x.to_json() for x in self.endpoints] + + response = DynectSession.post(self.uri, api_args) self._build(response['data']) def _build(self, data): @@ -1905,129 +1572,46 @@ def _build(self, data): :param data: The ``['data']`` field from an API JSON response """ - for key, val in data.items(): - if key == 'endpoints': - self._endpoints = [] - for endpoint in val: - ep = DSFMonitorEndpoint(**endpoint) - ep._monitor = self - self._endpoints.append(ep) - elif key == 'options': - for opt_key, opt_val in val.items(): - setattr(self, '_' + opt_key, opt_val) - elif key == 'active': - self._active = Active(val) - else: - setattr(self, '_' + key, val) - - @property - def dsf_monitor_id(self): - """The unique system id of this :class:`DSFMonitor`""" - return self._dsf_monitor_id - @dsf_monitor_id.setter - def dsf_monitor_id(self, value): - pass + if 'endpoints' in data: + endpoints = data.pop('endpoints') + self._endpoints = [] + for endpoint in endpoints: + ep = DSFMonitorEndpoint(**endpoint) + ep._monitor = self + self._endpoints.append(ep) + if 'options' in data: + options = data.pop('options') + for key, val in options.items(): + data[key] = val + if 'active' in data: + self._active = Active(data.pop('active')) + super(DSFMonitor, self)._build(data) + self.uri = '/DSFMonitor/{0}/'.format(self.dsf_monitor_id) - @property - def label(self): - """A unique label to identify this :class:`DSFMonitor`""" - return self._label - @label.setter - def label(self, value): - self._label = value - api_args = {'label': self._label} - self._update(api_args) - - @property - def protocol(self): - """The protocol to monitor. Must be one of 'HTTP', 'HTTPS', 'PING', - 'SMTP', or 'TCP' - """ - return self._protocol - @protocol.setter - def protocol(self, value): - self._protocol = value - api_args = {'protocol': self._protocol} - self._update(api_args) - - @property - def response_count(self): - """The number of responses to determine whether or not the endpoint is - 'up' or 'down' - """ - return self._response_count - @response_count.setter - def response_count(self, value): - self._response_count = value - api_args = {'response_count': self._response_count} - self._update(api_args) - - @property - def probe_interval(self): - """How often to run this :class:`DSFMonitor`""" - return self._probe_interval - @probe_interval.setter - def probe_interval(self, value): - self._probe_interval = value - api_args = {'probe_interval': self._probe_interval} - self._update(api_args) + def __str__(self): + return ': {0}'.format(self.dsf_monitor_id) - @property - def retries(self): - """How many retries this :class:`DSFMonitor` should attempt on failure - before giving up. - """ - return self._retries - @retries.setter - def retries(self, value): - self._retries = value - api_args = {'retries': self._retries} - self._update(api_args) - @property - def active(self): - """Returns whether or not this :class:`DSFMonitor` is active. Will - return either 'Y' or 'N' - """ - return self._active - @active.setter - def active(self, value): - self._active = value - api_args = {'active': self._active} - self._update(api_args) +class TrafficDirector(DNSAPIObject): + """Traffic Director is a DNS based traffic routing and load balancing + service that is Geolocation aware and can support failover by monitoring + endpoints. + """ + uri = '/DSF/' + _get_length = 1 - @property - def endpoints(self): - """A list of the endpoints (and their statuses) that this - :class:`DSFMonitor` is currently monitoring. - """ - self._get(self.dsf_monitor_id) - return self._endpoints - @endpoints.setter - def endpoints(self, value): - pass + #: The unique DynECT system id for this Traffic Director service + service_id = ImmutableAttribute('service_id') - @property - def options(self): - """Additional options pertaining to this :class:`DSFMonitor`""" - return self._options - @options.setter - def options(self, value): - self._options = value - api_args = {'options': self._options} - self._update(api_args) + #: A unique label describing this Traffic Director service + label = StringAttribute('label') - def delete(self): - """Delete an existing :class:`DSFMonitor` from the DynECT System""" - api_args = {} - DynectSession.get_session().execute(self.uri, 'DELETE', api_args) + #: The default TTL to be used across this service + ttl = IntegerAttribute('ttl') + #: A list of :class:`DSFRulesets` that are defined for this service + rulesets = ListAttribute('rulesets') -class TrafficDirector(object): - """Traffic Director is a DNS based traffic routing and load balancing - service that is Geolocation aware and can support failover by monitoring - endpoints. - """ def __init__(self, *args, **kwargs): """Create a :class:`TrafficDirector` object @@ -2041,117 +1625,83 @@ def __init__(self, *args, **kwargs): :param rulesets: A list of :class:`DSFRulesets` that are defined for this :class:`TrafficDirector` service """ - super(TrafficDirector, self).__init__() - self._label = self._ttl = self._publish = self._response_pools = None - self._record_sets = self.uri = self._service_id = None self._notifiers = APIList(DynectSession.get_session, 'notifiers') self._nodes = APIList(DynectSession.get_session, 'nodes') self._rulesets = APIList(DynectSession.get_session, 'rulesets') - if 'api' in kwargs: - del kwargs['api'] - self._build(kwargs) - elif len(args) + len(kwargs) == 1: - self._get(*args, **kwargs) - else: - self._post(*args, **kwargs) - self.uri = '/DSF/{}/'.format(self._service_id) + self._notifiers.uri = self._nodes.uri = self._rulesets.uri = self.uri + super(TrafficDirector, self).__init__(*args, **kwargs) + + self.uri = '/DSF/{service_id}'.format(service_id=self.service_id) self._notifiers.uri = self._nodes.uri = self._rulesets.uri = self.uri def _post(self, label, ttl=None, publish='Y', nodes=None, notifiers=None, rulesets=None): """Create a new :class:`TrafficDirector` on the DynECT System""" uri = '/DSF/' - self._label = label - self._ttl = ttl - self._nodes = nodes - self._notifiers = notifiers - self._rulesets = rulesets - api_args = {'label': self._label, - 'publish': publish} + api_args = {'label': label, 'publish': publish} if ttl: - api_args['ttl'] = self._ttl + api_args['ttl'] = ttl if nodes: - api_args['nodes'] = self._nodes + api_args['nodes'] = nodes if notifiers: if isinstance(notifiers[0], dict): api_args['notifiers'] = notifiers else: # notifiers is a list of Notifier objects - api_args['notifiers'] = [{'notifier_id': x.notifier_id} for x - in self._notifiers] + api_args['notifiers'] = [{'notifier_id': n.notifier_id} for n + in notifiers] if rulesets: - api_args['rulesets'] = [rule._json for rule in self._rulesets] - response = DynectSession.get_session().execute(uri, 'POST', api_args) - self.uri = '/DSF/{}/'.format(response['data']['service_id']) + api_args['rulesets'] = [rule.to_json() for rule in rulesets] + response = DynectSession.post(uri, api_args) + self.uri = '/DSF/{0}/'.format(response['data']['service_id']) self._build(response['data']) def _build(self, data): - for key, val in data.items(): - if key == 'notifiers': - # Don't do anything special with these dicts for now - self._notifiers = APIList(DynectSession.get_session, - 'notifiers', None, val) - elif key == 'rulesets': - # Clear Rulesets - self._rulesets = APIList(DynectSession.get_session, 'rulesets') - self._rulesets.uri = None - # For each Ruleset returned, create a new DSFRuleset object - for ruleset in val: - self._rulesets.append(DSFRuleset(**ruleset)) - elif key == 'nodes': - # Don't do anything special with these dicts for now - self._nodes = APIList(DynectSession.get_session, 'nodes', None, - val) - else: - setattr(self, '_' + key, val) - self.uri = '/DSF/{}/'.format(self._service_id) + if 'notifiers' in data: + self._notifiers = APIList(DynectSession.get_session, 'notifiers', + None, data.pop('notifiers')) + if 'rulesets' in data: + self._rulesets = APIList(DynectSession.get_session, 'rulesets') + self._rulesets.uri = None + # For each Ruleset returned, create a new DSFRuleset object + for ruleset in data.pop('rulesets'): + for pool in ruleset.get('response_pools', []): + pool['dsf_id'] = self.service_id + self._rulesets.append(DSFRuleset(**ruleset)) + if 'nodes' in data: + self._nodes = APIList(DynectSession.get_session, 'nodes', None, + data.pop('nodes')) self._notifiers.uri = self._nodes.uri = self._rulesets.uri = self.uri + super(TrafficDirector, self)._build(data) def _get(self, service_id): """Get an existing :class:`TrafficDirector` from the DynECT System""" - self._service_id = service_id - self.uri = '/DSF/{}/'.format(self._service_id) + self.uri = '/DSF/{0}/'.format(service_id) api_args = {'pending_changes': 'Y'} - response = DynectSession.get_session().execute(self.uri, 'GET', - api_args) - self._build(response['data']) - - def _update(self, api_args): - """Private update method""" - if 'publish' not in api_args: - api_args['publish'] = 'Y' - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) + resp = DynectSession.get(self.uri, api_args) + self._build(resp['data']) def revert_changes(self): """Clears the changeset for this service and reverts all non-published changes to their original state """ - api_args = {'revert': True} - self._update(api_args) + self._update(revert=True) def add_notifier(self, notifier_id, publish='Y'): """Links the Notifier with the specified id to this Traffic Director service """ - api_args = {'add_notifier': True, 'notifier_id': notifier_id, - 'publish': publish} - self._update(api_args) + self._update(add_notifier=True, notifier_id=notifier_id, + publish=publish) def remove_orphans(self): """Remove Record Set Chains which are no longer referenced by a :class:`DSFResponsePool` """ - api_args = {'remove_orphans': 'Y'} - self._update(api_args) + self._update(remove_orphans='Y') - @property - def service_id(self): - """The unique System id of this DSF Service""" - return self._service_id - @service_id.setter - def service_id(self, value): - pass + def publish(self): + """Publish any pending changesets for this service""" + self._update(publish='Y') @property def records(self): @@ -2161,9 +1711,6 @@ def records(self): for rs_chains in response_pool.rs_chains for record_set in rs_chains.record_sets for record in record_set.records] - @records.setter - def records(self, value): - pass @property def record_sets(self): @@ -2174,9 +1721,6 @@ def record_sets(self): for response_pool in ruleset.response_pools for rs_chains in response_pool.rs_chains for record_set in rs_chains.record_sets] - @record_sets.setter - def record_sets(self, value): - pass @property def response_pools(self): @@ -2185,9 +1729,6 @@ def response_pools(self): """ return [response_pool for ruleset in self._rulesets for response_pool in ruleset.response_pools] - @response_pools.setter - def response_pools(self, value): - pass @property def rs_chains(self): @@ -2197,86 +1738,6 @@ def rs_chains(self): return [rs_chains for ruleset in self._rulesets for response_pool in ruleset.response_pools for rs_chains in response_pool.rs_chains] - @rs_chains.setter - def rs_chains(self, value): - pass - - @property - def notifiers(self): - """A list of names of notifiers associated with this - :class:`TrafficDirector` service - """ - return self._notifiers - @notifiers.setter - def notifiers(self, value): - if isinstance(value, list) and not isinstance(value, APIList): - self._notifiers = APIList(DynectSession.get_session, 'notifiers', - None, value) - elif isinstance(value, APIList): - self._notifiers = value - self._notifiers.uri = self.uri - - @property - def rulesets(self): - """A list of :class:`DSFRulesets` that are defined for this - :class:`TrafficDirector` service - """ - return self._rulesets - @rulesets.setter - def rulesets(self, value): - if isinstance(value, list) and not isinstance(value, APIList): - self._rulesets = APIList(DynectSession.get_session, 'rulesets', - None, value) - elif isinstance(value, APIList): - self._rulesets = value - self._rulesets.uri = self.uri - - @property - def nodes(self): - """A list of zone, FQDN pairs in a hash that are linked, or to be linked - to this :class:`TrafficDirector` service""" - return self._nodes - @nodes.setter - def nodes(self, value): - if isinstance(value, list) and not isinstance(value, APIList): - self._nodes = APIList(DynectSession.get_session, 'nodes', None, - value) - elif isinstance(value, APIList): - self._nodes = value - self._nodes.uri = self.uri - - @property - def label(self): - """A unique label for this :class:`TrafficDirector` service""" - return self._label - @label.setter - def label(self, value): - self._label = value - api_args = {'label': self._label} - self._update(api_args) - - @property - def ttl(self): - """The default TTL to be used across this service""" - if not isinstance(self._ttl, int): - self._ttl = int(self._ttl) - return self._ttl - @ttl.setter - def ttl(self, value): - self._ttl = value - api_args = {'ttl': self._ttl} - self._update(api_args) - - def delete(self): - """Delete this :class:`TrafficDirector` from the DynECT System""" - api_args = {} - DynectSession.get_session().execute(self.uri, 'DELETE', api_args) def __str__(self): - """str override""" - return force_unicode(': {}').format(self._service_id) - __repr__ = __unicode__ = __str__ - - def __bytes__(self): - """bytes override""" - return bytes(self.__str__()) + return force_unicode(': {0}').format(self.service_id) diff --git a/dyn/tm/services/geo.py b/dyn/tm/services/geo.py deleted file mode 100644 index bf4be08..0000000 --- a/dyn/tm/services/geo.py +++ /dev/null @@ -1,666 +0,0 @@ -# -*- coding: utf-8 -*- -"""This module contains API wrappers for the Geo TM service. -NOTE: The Geo Traffic Management Service is deprecated. Legacy users of the -service should contact Concierge for any questions on adding a Geo Traffic -Management service to your zone. All other users should use Traffic Director -(DSF) instead. -""" -import logging - -from ..records import * -from ..session import DynectSession - -__author__ = 'jnappi' -__all__ = ['GeoARecord', 'GeoAAAARecord', 'GeoCERTRecord', 'GeoCNAMERecord', - 'GeoDHCIDRecord', 'GeoDNAMERecord', 'GeoDNSKEYRecord', 'GeoDSRecord', - 'GeoKEYRecord', 'GeoKXRecord', 'GeoLOCRecord', 'GeoIPSECKEYRecord', - 'GeoMXRecord', 'GeoNAPTRRecord', 'GeoPTRRecord', 'GeoPXRecord', - 'GeoNSAPRecord', 'GeoRPRecord', 'GeoNSRecord', 'GeoSPFRecord', - 'GeoSRVRecord', 'GeoTXTRecord', 'GeoRegionGroup', 'Geo'] - - -class GeoARecord(ARecord): - """An :class:`ARecord` object which is able to store additional data for - use by a :class:`Geo` service. - """ - def __init__(self, weight, serve_count, label=None, ttl=None, *args, - **kwargs): - """Create a :class:`GeoARecord` object - - :param weight: The weight for this :class:`GeoARecord` - :param serve_count: The serve count for this :class:`GeoARecord` - :param *args: Non keyword args for the associated :class:`ARecord` - :param label: A unique label for this :class:`GeoARecord` - :param ttl: TTL for the associated :class:`ARecord`, will override a ttl - specified in *args or **kwargs - :param **kwargs: Keyword args for the associated :class:`ARecord` - """ - # Set api flag so the record isn't really created - kwargs['create'] = False - super(GeoARecord, self).__init__(*args, **kwargs) - self.weight = weight - self.serve_count = serve_count - self.label = label - self._ttl = ttl - - -class GeoAAAARecord(AAAARecord): - """An :class:`AAAARecord` object which is able to store additional data for - use by a :class:`Geo` service. - """ - def __init__(self, weight, serve_count, label=None, ttl=None, *args, - **kwargs): - """Create a :class:`GeoAAAARecord` object - - :param weight: The weight for this :class:`GeoAAAARecord` - :param serve_count: The serve count for this :class:`GeoAAAARecord` - :param *args: Non keyword args for the associated :class:`AAAARecord` - :param label: A unique label for this :class:`GeoAAAARecord` - :param ttl: TTL for the associated :class:`AAAARecord` - :param **kwargs: Keyword args for the associated :class:`AAAARecord` - """ - # Set api flag so the record isn't really created - kwargs['create'] = False - super(GeoAAAARecord, self).__init__(*args, **kwargs) - self.weight = weight - self.serve_count = serve_count - self.label = label - self._ttl = ttl - - -class GeoCERTRecord(CERTRecord): - """An :class:`CERTRecord` object which is able to store additional data for - use by a :class:`Geo` service. - """ - def __init__(self, label=None, ttl=None, *args, **kwargs): - """Create a :class:`GeoCERTRecord` object - - :param *args: Non keyword args for the associated :class:`CERTRecord` - :param label: A unique label for this :class:`GeoCERTRecord` - :param ttl: TTL for the associated :class:`CERTRecord` - :param **kwargs: Keyword args for the associated :class:`CERTRecord` - """ - # Set api flag so the record isn't really created - kwargs['create'] = False - super(GeoCERTRecord, self).__init__(*args, **kwargs) - self.label = label - self._ttl = ttl - - -class GeoCNAMERecord(CNAMERecord): - """An :class:`CNAMERecord` object which is able to store additional data for - use by a :class:`Geo` service. - """ - def __init__(self, weight, label=None, ttl=None, *args, **kwargs): - """Create a :class:`GeoCNAMERecord` object - - :param weight: The weight for this :class:`GeoCNAMERecord` - :param *args: Non keyword args for the associated :class:`CNAMERecord` - :param label: A unique label for this :class:`GeoCNAMERecord` - :param ttl: TTL for the associated :class:`CNAMERecord` - :param **kwargs: Keyword args for the associated :class:`CNAMERecord` - """ - # Set api flag so the record isn't really created - kwargs['create'] = False - super(GeoCNAMERecord, self).__init__(*args, **kwargs) - self.weight = weight - self.label = label - self._ttl = ttl - - -class GeoDHCIDRecord(DHCIDRecord): - """An :class:`DHCIDRecord` object which is able to store additional data for - use by a :class:`Geo` service. - """ - def __init__(self, label=None, ttl=None, *args, **kwargs): - """Create a :class:`GeoCNAMERecord` object - - :param *args: Non keyword args for the associated :class:`DHCIDRecord` - :param label: A unique label for this :class:`GeoDHCIDRecord` - :param ttl: TTL for the associated :class:`DHCIDRecord` - :param **kwargs: Keyword args for the associated :class:`DHCIDRecord` - """ - # Set api flag so the record isn't really created - kwargs['create'] = False - super(GeoDHCIDRecord, self).__init__(*args, **kwargs) - self.label = label - self._ttl = ttl - - -class GeoDNAMERecord(DNAMERecord): - """An :class:`DNAMERecord` object which is able to store additional data for - use by a :class:`Geo` service. - """ - def __init__(self, label=None, ttl=None, *args, **kwargs): - """Create a :class:`GeoDNAMERecord` object - - :param *args: Non keyword args for the associated :class:`DNAMERecord` - :param label: A unique label for this :class:`GeoDNAMERecord` - :param ttl: TTL for the associated :class:`DNAMERecord` - :param **kwargs: Keyword args for the associated :class:`DNAMERecord` - """ - # Set api flag so the record isn't really created - kwargs['create'] = False - super(GeoDNAMERecord, self).__init__(*args, **kwargs) - self.label = label - self._ttl = ttl - - -class GeoDNSKEYRecord(DNSKEYRecord): - """An :class:`DNSKEYRecord` object which is able to store additional data - for use by a :class:`Geo` service. - """ - def __init__(self, label=None, ttl=None, *args, **kwargs): - """Create a :class:`GeoDNSKEYRecord` object - - :param *args: Non keyword args for the associated :class:`DNSKEYRecord` - :param label: A unique label for this :class:`GeoDNSKEYRecord` - :param ttl: TTL for the associated :class:`DNSKEYRecord` - :param **kwargs: Keyword args for the associated :class:`DNSKEYRecord` - """ - # Set api flag so the record isn't really created - kwargs['create'] = False - super(GeoDNSKEYRecord, self).__init__(*args, **kwargs) - self.label = label - self._ttl = ttl - - -class GeoDSRecord(DSRecord): - """An :class:`DSRecord` object which is able to store additional data for - use by a :class:`Geo` service. - """ - def __init__(self, label=None, ttl=None, *args, **kwargs): - """Create a :class:`GeoDSRecord` object - - :param *args: Non keyword args for the associated :class:`DSRecord` - :param label: A unique label for this :class:`GeoDSRecord` - :param ttl: TTL for the associated :class:`DSRecord`, will override a - ttl specified in *args or **kwargs - :param **kwargs: Keyword args for the associated :class:`DSRecord` - """ - # Set api flag so the record isn't really created - kwargs['create'] = False - super(GeoDSRecord, self).__init__(*args, **kwargs) - self.label = label - self._ttl = ttl - - -class GeoKEYRecord(KEYRecord): - """An :class:`KEYRecord` object which is able to store additional data for - use by a :class:`Geo` service. - """ - def __init__(self, label=None, ttl=None, *args, **kwargs): - """Create a :class:`GeoKEYRecord` object - - :param *args: Non keyword args for the associated :class:`KEYRecord` - :param label: A unique label for this :class:`GeoKEYRecord` - :param ttl: TTL for the associated :class:`KEYRecord`, will override a - ttl specified in *args or **kwargs - :param **kwargs: Keyword args for the associated :class:`KEYRecord` - """ - # Set api flag so the record isn't really created - kwargs['create'] = False - super(GeoKEYRecord, self).__init__(*args, **kwargs) - self.label = label - self._ttl = ttl - - -class GeoKXRecord(KXRecord): - """An :class:`KXRecord` object which is able to store additional data for - use by a :class:`Geo` service. - """ - def __init__(self, label=None, ttl=None, *args, **kwargs): - """Create a :class:`GeoKXRecord` object - - :param *args: Non keyword args for the associated :class:`KXRecord` - :param label: A unique label for this :class:`GeoKXRecord` - :param ttl: TTL for the associated :class:`KXRecord`, will override a - ttl specified in *args or **kwargs - :param **kwargs: Keyword args for the associated :class:`KXRecord` - """ - # Set api flag so the record isn't really created - kwargs['create'] = False - super(GeoKXRecord, self).__init__(*args, **kwargs) - self.label = label - self._ttl = ttl - - -class GeoLOCRecord(LOCRecord): - """An :class:`LOCRecord` object which is able to store additional data for - use by a :class:`Geo` service. - """ - def __init__(self, label=None, ttl=None, *args, **kwargs): - """Create a :class:`GeoLOCRecord` object - - :param *args: Non keyword args for the associated :class:`LOCRecord` - :param label: A unique label for this :class:`GeoLOCRecord` - :param ttl: TTL for the associated :class:`LOCRecord`, will override a - ttl specified in *args or **kwargs - :param **kwargs: Keyword args for the associated :class:`LOCRecord` - """ - # Set api flag so the record isn't really created - kwargs['create'] = False - super(GeoLOCRecord, self).__init__(*args, **kwargs) - self.label = label - self._ttl = ttl - - -class GeoIPSECKEYRecord(IPSECKEYRecord): - """An :class:`IPSECKEYRecord` object which is able to store additional data - for use by a :class:`Geo` service. - """ - def __init__(self, label=None, ttl=None, *args, **kwargs): - """Create a :class:`GeoIPSECKEYRecord` object - - :param *args: Non keyword args for the associated - :class:`IPSECKEYRecord` - :param label: A unique label for this :class:`GeoIPSECKEYRecord` - :param ttl: TTL for the associated :class:`IPSECKEYRecord`, will - override a ttl specified in *args or **kwargs - :param **kwargs: Keyword args for the associated :class:`IPSECKEYRecord` - """ - # Set api flag so the record isn't really created - kwargs['create'] = False - super(GeoIPSECKEYRecord, self).__init__(*args, **kwargs) - self.label = label - self._ttl = ttl - - -class GeoMXRecord(MXRecord): - """An :class:`MXRecord` object which is able to store additional data for - use by a :class:`Geo` service. - """ - def __init__(self, label=None, ttl=None, *args, **kwargs): - """Create a :class:`GeoMXRecord` object - - :param *args: Non keyword args for the associated :class:`MXRecord` - :param label: A unique label for this :class:`GeoMXRecord` - :param ttl: TTL for the associated :class:`MXRecord`, will override a - ttl specified in *args or **kwargs - :param **kwargs: Keyword args for the associated :class:`MXRecord` - """ - # Set api flag so the record isn't really created - kwargs['create'] = False - super(GeoMXRecord, self).__init__(*args, **kwargs) - self.label = label - self._ttl = ttl - - -class GeoNAPTRRecord(NAPTRRecord): - """An :class:`NAPTRRecord` object which is able to store additional data for - use by a :class:`Geo` service. - """ - def __init__(self, label=None, ttl=None, *args, **kwargs): - """Create a :class:`GeoNAPTRRecord` object - - :param *args: Non keyword args for the associated :class:`NAPTRRecord` - :param label: A unique label for this :class:`GeoNAPTRRecord` - :param ttl: TTL for the associated :class:`NAPTRRecord`, will override a - ttl specified in *args or **kwargs - :param **kwargs: Keyword args for the associated :class:`NAPTRRecord` - """ - # Set api flag so the record isn't really created - kwargs['create'] = False - super(GeoNAPTRRecord, self).__init__(*args, **kwargs) - self.label = label - self._ttl = ttl - - -class GeoPTRRecord(PTRRecord): - """An :class:`PTRRecord` object which is able to store additional data for - use by a :class:`Geo` service. - """ - def __init__(self, label=None, ttl=None, *args, **kwargs): - """Create a :class:`GeoPTRRecord` object - - :param *args: Non keyword args for the associated :class:`PTRRecord` - :param label: A unique label for this :class:`GeoPTRRecord` - :param ttl: TTL for the associated :class:`PTRRecord`, will override a - ttl specified in *args or **kwargs - :param **kwargs: Keyword args for the associated :class:`PTRRecord` - """ - # Set api flag so the record isn't really created - kwargs['create'] = False - super(GeoPTRRecord, self).__init__(*args, **kwargs) - self.label = label - self._ttl = ttl - - -class GeoPXRecord(PXRecord): - """An :class:`PXRecord` object which is able to store additional data for - use by a :class:`Geo` service. - """ - def __init__(self, label=None, ttl=None, *args, **kwargs): - """Create a :class:`GeoPXRecord` object - - :param *args: Non keyword args for the associated :class:`PXRecord` - :param label: A unique label for this :class:`GeoPXRecord` - :param ttl: TTL for the associated :class:`PXRecord`, will override a - ttl specified in *args or **kwargs - :param **kwargs: Keyword args for the associated :class:`PXRecord` - """ - # Set api flag so the record isn't really created - kwargs['create'] = False - super(GeoPXRecord, self).__init__(*args, **kwargs) - self.label = label - self._ttl = ttl - - -class GeoNSAPRecord(NSAPRecord): - """An :class:`NSAPRecord` object which is able to store additional data for - use by a :class:`Geo` service. - """ - def __init__(self, label=None, ttl=None, *args, **kwargs): - """Create a :class:`GeoNSAPRecord` object - - :param *args: Non keyword args for the associated :class:`NSAPRecord` - :param label: A unique label for this :class:`GeoNSAPRecord` - :param ttl: TTL for the associated :class:`NSAPRecord`, will override a - ttl specified in *args or **kwargs - :param **kwargs: Keyword args for the associated :class:`NSAPRecord` - """ - # Set api flag so the record isn't really created - kwargs['create'] = False - super(GeoNSAPRecord, self).__init__(*args, **kwargs) - self.label = label - self._ttl = ttl - - -class GeoRPRecord(RPRecord): - """An :class:`RPRecord` object which is able to store additional data for - use by a :class:`Geo` service. - """ - def __init__(self, label=None, ttl=None, *args, **kwargs): - """Create a :class:`GeoRPRecord` object - - :param *args: Non keyword args for the associated :class:`RPRecord` - :param label: A unique label for this :class:`GeoRPRecord` - :param ttl: TTL for the associated :class:`RPRecord`, will override a - ttl specified in *args or **kwargs - :param **kwargs: Keyword args for the associated :class:`RPRecord` - """ - # Set api flag so the record isn't really created - kwargs['create'] = False - super(GeoRPRecord, self).__init__(*args, **kwargs) - self.label = label - self._ttl = ttl - - -class GeoNSRecord(NSRecord): - """An :class:`NSRecord` object which is able to store additional data for - use by a :class:`Geo` service. - """ - def __init__(self, label=None, ttl=None, *args, **kwargs): - """Create a :class:`GeoNSRecord` object - - :param *args: Non keyword args for the associated :class:`NSRecord` - :param label: A unique label for this :class:`GeoNSRecord` - :param ttl: TTL for the associated :class:`NSRecord`, will override a - ttl specified in *args or **kwargs - :param **kwargs: Keyword args for the associated :class:`NSRecord` - """ - # Set api flag so the record isn't really created - kwargs['create'] = False - super(GeoNSRecord, self).__init__(*args, **kwargs) - self.label = label - self._ttl = ttl - - -class GeoSPFRecord(SPFRecord): - """An :class:`SPFRecord` object which is able to store additional data for - use by a :class:`Geo` service. - """ - def __init__(self, label=None, ttl=None, *args, **kwargs): - """Create a :class:`GeoSPFRecord` object - - :param *args: Non keyword args for the associated :class:`SPFRecord` - :param label: A unique label for this :class:`GeoSPFRecord` - :param ttl: TTL for the associated :class:`SPFRecord`, will override a - ttl specified in *args or **kwargs - :param **kwargs: Keyword args for the associated :class:`SPFRecord` - """ - # Set api flag so the record isn't really created - kwargs['create'] = False - super(GeoSPFRecord, self).__init__(*args, **kwargs) - self.label = label - self._ttl = ttl - - -class GeoSRVRecord(SRVRecord): - """An :class:`SRVRecord` object which is able to store additional data for - use by a :class:`Geo` service. - """ - def __init__(self, label=None, ttl=None, *args, **kwargs): - """Create a :class:`GeoSRVRecord` object - - :param *args: Non keyword args for the associated :class:`SRVRecord` - :param label: A unique label for this :class:`GeoSRVRecord` - :param ttl: TTL for the associated :class:`SRVRecord`, will override a - ttl specified in *args or **kwargs - :param **kwargs: Keyword args for the associated :class:`SRVRecord` - """ - # Set api flag so the record isn't really created - kwargs['create'] = False - super(GeoSRVRecord, self).__init__(*args, **kwargs) - self.label = label - self._ttl = ttl - - -class GeoTXTRecord(TXTRecord): - """An :class:`TXTRecord` object which is able to store additional data for - use by a :class:`Geo` service. - """ - def __init__(self, label=None, ttl=None, *args, **kwargs): - """Create a :class:`GeoTXTRecord` object - - :param *args: Non keyword args for the associated :class:`TXTRecord` - :param label: A unique label for this :class:`GeoTXTRecord` - :param ttl: TTL for the associated :class:`TXTRecord`, will override a - ttl specified in *args or **kwargs - :param **kwargs: Keyword args for the associated :class:`TXTRecord` - """ - # Set api flag so the record isn't really created - kwargs['create'] = False - super(GeoTXTRecord, self).__init__(*args, **kwargs) - self.label = label - self._ttl = ttl - - -class GeoRegionGroup(object): - """docstring for GeoRegionGroup""" - def __init__(self, countries, name, geo_records, **kwargs): - """Create a :class:`GeoRegionGroup` object - - :param countries: A list of ISO-3166 two letter codes to represent the - names of countries and their subdivisions or one of the predefined - groups. - :param name: Name of this :class:`GeoRegionGroup` - :param geo_records: A `list` of custom Geo DNSRecord subclass type - objects - """ - super(GeoRegionGroup, self).__init__() - self.uri = None - self._service_name = self._group_name = self._countries = None - self._countries = countries - self._name = name - self.geo_records = geo_records - # TODO: implement return data (GET) object creation - if 'api' in kwargs: - del kwargs['api'] - for key, val in kwargs.items(): - setattr(self, '_' + key, val) - - def _post(self, countries, name, geo_records): - self._countries = countries - self._name = name - self.geo_records = geo_records - api_args = {'service_name': self._service_name, - 'group_name': self._group_name, - 'countries': self._countries, 'name': self._name} - return api_args - - def _get(self): - api_args = {} - response = DynectSession.get_session().execute(self.uri, 'GET', - api_args) - for key, val in response['data'].items(): - setattr(self, '_' + key, val) - - @property - def countries(self): - return self._countries - - def delete(self): - api_args = {} - DynectSession.get_session().execute(self.uri, 'DELETE', api_args) - - -class Geo(object): - """docstring for Geo""" - def __init__(self, service_name, *args, **kwargs): - """Create a new :class:`Geo` object - - :param service_name: The name of this Geo Service - :param groups: A list of :class:`GeoRegionGroup`'s - :param nodes: A list of :class:`GeoNode`'s - :param ttl: Time to Live for each record in the service - """ - super(Geo, self).__init__() - self._service_name = service_name - self.uri = '/Geo/{}/'.format(self._service_name) - self._groups = self._nodes = self._ttl = None - if 'api' in kwargs: - del kwargs['api'] - for key, val in kwargs.items(): - setattr(self, '_' + key, val) - elif len(args) == 0 and len(kwargs) == 0: - self._get() - else: - self._post(*args, **kwargs) - - def _post(self, groups, ttl=None): - """Create a new :class:`Geo` service on the DynECT System""" - self._groups = groups - self._ttl = ttl - api_args = {'groups': []} - nodes = [] - for group in groups: - weight = {} - serve_count = {} - ttls = {} - label = {} - rdata = {} - for record in group.geo_records: - weight_name = ''.join([record.rec_name, '_weight']) - serve_name = ''.join([record.rec_name, '_serve_count']) - ttl_name = ''.join([record.rec_name, '_ttl']) - label_name = ''.join([record.rec_name, '_label']) - rdata_name = ''.join([record.rec_name, '_rdata']) - # Build weight hash - if hasattr(record, 'weight'): - if weight_name in weight: - weight[weight_name].append(record.weight) - else: - weight[weight_name] = [record.weight] - # Build serve_count hash - if hasattr(record, 'serve_count'): - serve_count[serve_name] = str(record.serve_count) - # Build ttl hash - if ttl_name in serve_count: - ttls[ttl_name] = str(record.ttl) or str(self._ttl) - # Build label hash - autolabel = '' - if label_name in label: - label[label_name].append(record.label or autolabel) - else: - label[label_name] = [record.label or autolabel] - # Build rdata hash - if rdata_name in rdata: - rdata[rdata_name].append(record.geo_rdata) - else: - rdata[rdata_name] = [record.geo_rdata] - nodes.append(record.geo_node) - group_data = {'weight': weight, 'serve_count': serve_count, - 'ttl': ttls, 'label': label, 'rdata': rdata, - 'countries': group.countries, 'name': group._name} - api_args['groups'].append(group_data) - if self._ttl: - api_args['ttl'] = self._ttl - api_args['nodes'] = nodes - response = DynectSession.get_session().execute(self.uri, 'POST', - api_args) - for key, val in response['data'].items(): - if key == 'groups': - pass - elif key == 'nodes': - pass - else: - setattr(self, '_' + key, val) - - def _get(self): - """Get an existing :class:`Geo` service from the DynECT System""" - api_args = {} - response = DynectSession.get_session().execute(self.uri, 'GET', - api_args) - for key, val in response['data'].items(): - if key == 'groups': - pass - elif key == 'nodes': - pass - else: - setattr(self, '_' + key, val) - - def _update(self, api_args): - """Private Update method""" - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - for key, val in response['data'].items(): - if key == 'groups': - pass - elif key == 'nodes': - pass - else: - setattr(self, '_' + key, val) - - @property - def service_name(self): - return self._service_name - @service_name.setter - def service_name(self, new_name): - self._service_name = new_name - api_args = {'new_name': self._service_name} - self._update(api_args) - self.uri = '/Geo/{}/'.format(self._service_name) - - @property - def groups(self): - return self._groups - @groups.setter - def groups(self, value): - self._groups = value - api_args = {'groups': self._groups.to_json()} - self._update(api_args) - - @property - def nodes(self): - return self._nodes - @nodes.setter - def nodes(self, value): - self._nodes = value - api_args = {'nodes': self._nodes.to_json()} - self._update(api_args) - - def activate(self): - """Activate this :class:`Geo` service on the DynECT System""" - api_args = {'activate': True} - self._update(api_args) - - def deactivate(self): - """Deactivate this :class:`Geo` service on the DynECT System""" - api_args = {'deactivate': True} - self._update(api_args) - - def delete(self): - """Delete this :class:`Geo` service from the DynECT System""" - api_args = {} - DynectSession.get_session().execute(self.uri, 'DELETE', api_args) diff --git a/dyn/tm/services/gslb.py b/dyn/tm/services/gslb.py index 2826e1d..966f677 100644 --- a/dyn/tm/services/gslb.py +++ b/dyn/tm/services/gslb.py @@ -1,213 +1,36 @@ # -*- coding: utf-8 -*- -import logging - +from ._shared import BaseMonitor from ..utils import APIList -from ..errors import DynectInvalidArgumentError -from ..session import DynectSession +from ..session import DynectSession, DNSAPIObject +from ...core import (APIService, ImmutableAttribute, StringAttribute, + ValidatedAttribute, IntegerAttribute, ClassAttribute, + ListAttribute) from ...compat import force_unicode __author__ = 'jnappi' -__all__ = ['Monitor', 'GSLBRegionPoolEntry', 'GSLBRegion', 'GSLB'] - - -class Monitor(object): - """A :class:`Monitor` for a GSLB Service""" - def __init__(self, protocol, interval, retries=None, timeout=None, - port=None, path=None, host=None, header=None, expected=None): - """Create a :class:`Monitor` object - - :param protocol: The protocol to monitor. Must be either HTTP, HTTPS, - PING, SMTP, or TCP - :param interval: How often (in minutes) to run the monitor. Must be 1, - 5, 10, or 15, - :param retries: The number of retries the monitor attempts on failure - before giving up - :param timeout: The amount of time in seconds before the connection - attempt times out - :param port: For HTTP(S)/SMTP/TCP probes, an alternate connection port - :param path: For HTTP(S) probes, a specific path to request - :param host: For HTTP(S) probes, a value to pass in to the Host - :param header: For HTTP(S) probes, additional header fields/values to - pass in, separated by a newline character. - :param expected: For HTTP(S) probes, a string to search for in the - response. For SMTP probes, a string to compare the banner against. - Failure to find this string means the monitor will report a down - status. - """ - super(Monitor, self).__init__() - self._protocol = protocol - self._interval = interval - self._retries = retries - self._timeout = timeout - self._port = port - self._path = path - self._host = host - self._header = header - self._expected = expected - self.zone = None - self.fqdn = None - self.valid_protocols = ('HTTP', 'HTTPS', 'PING', 'SMTP', 'TCP') - self.valid_intervals = (1, 5, 10, 15) - self.valid_timeouts = (10, 15, 25, 30) - - def to_json(self): - """Convert this :class:`HealthMonitor` object to a JSON blob""" - json_blob = {'protocol': self._protocol, - 'interval': self._interval} - for key, val in self.__dict__.items(): - if val is not None and not hasattr(val, '__call__') and \ - key.startswith('_'): - json_blob[key[1:]] = val - return json_blob - - def __eq__(self, other): - """eq override for comparing :class:`HealthMonitor` objects to JSON - response hashes or other :class:`HealthMonitor` instances - - :param other: the value to compare this :class:`HealthMonitor` to. Valid - input types: `dict`, :class:`HealthMonitor` - """ - if isinstance(other, dict): - return False - elif isinstance(other, Monitor): - return False - else: - return False - - @property - def status(self): - """Get the current status of this :class:`HealthMonitor` from the DynECT - System - """ - api_args = {} - uri = '/Failover/{}/{}/'.format(self.zone, self.fqdn) - respnose = DynectSession.get_session().execute(uri, 'GET', api_args) - return respnose['data']['status'] +__all__ = ['GSLBMonitor', 'GSLBRegionPoolEntry', 'GSLBRegion', 'GSLB'] - @property - def protocol(self): - """The protocol to monitor""" - return self._protocol - @protocol.setter - def protocol(self, value): - if value not in self.valid_protocols: - raise Exception - self._protocol = value - api_args = {'monitor': {'protocol': self._protocol}} - uri = '/Failover/{}/{}/'.format(self.zone, self.fqdn) - DynectSession.get_session().execute(uri, 'PUT', api_args) - @property - def interval(self): - """How often to run this monitor""" - return self._interval - @interval.setter - def interval(self, value): - if value not in self.valid_intervals: - raise Exception - self._interval = value - api_args = {'monitor': {'interval': self._interval}} - uri = '/Failover/{}/{}/'.format(self.zone, self.fqdn) - DynectSession.get_session().execute(uri, 'PUT', api_args) +class GSLBMonitor(BaseMonitor): + """A :class:`GSLBMonitor` for a GSLB Service""" @property - def retries(self): - """The number of retries the monitor attempts on failure before giving - up - """ - return self._retries - @retries.setter - def retries(self, value): - self._retries = value - api_args = {'monitor': {'retries': self._retries}} - uri = '/Failover/{}/{}/'.format(self.zone, self.fqdn) - DynectSession.get_session().execute(uri, 'PUT', api_args) + def uri(self): + if self.zone is not None and self.fqdn is not None: + return '/GSLB/{0}/{1}/'.format(self.zone, self.fqdn) + raise ValueError - @property - def timeout(self): - """The amount of time in seconds before the connection attempt times - out - """ - return self._timeout - @timeout.setter - def timeout(self, value): - self._timeout = value - api_args = {'monitor': {'timeout': self._timeout}} - uri = '/Failover/{}/{}/'.format(self.zone, self.fqdn) - DynectSession.get_session().execute(uri, 'PUT', api_args) - @property - def port(self): - """For HTTP(S)/SMTP/TCP probes, an alternate connection port""" - return self._port - @port.setter - def port(self, value): - self._port = value - api_args = {'monitor': {'port': self._port}} - uri = '/Failover/{}/{}/'.format(self.zone, self.fqdn) - DynectSession.get_session().execute(uri, 'PUT', api_args) - - @property - def path(self): - """For HTTP(S) probes, a specific path to request""" - return self._path - @path.setter - def path(self, value): - self._path = value - api_args = {'monitor': {'path': self._path}} - uri = '/Failover/{}/{}/'.format(self.zone, self.fqdn) - DynectSession.get_session().execute(uri, 'PUT', api_args) +class GSLBRegionPoolEntry(DNSAPIObject): + uri = '/GSLBRegionPoolEntry/{zone}/{fqdn}/{region}/{address}/' + zone = ImmutableAttribute('zone') + fqdn = ImmutableAttribute('fqdn') + region_code = ImmutableAttribute('region_code') + label = StringAttribute('label') + weight = ValidatedAttribute('weight', validator=range(1, 15)) + serve_mode = ValidatedAttribute('serve_mode', validator=('always', 'obey', + 'remove', 'no')) - @property - def host(self): - """For HTTP(S) probes, a value to pass in to the Host""" - return self._host - @host.setter - def host(self, value): - self._host = value - api_args = {'monitor': {'host': self._host}} - uri = '/Failover/{}/{}/'.format(self.zone, self.fqdn) - DynectSession.get_session().execute(uri, 'PUT', api_args) - - @property - def header(self): - """For HTTP(S) probes, additional header fields/values to pass in, - separated by a newline character - """ - return self._header - @header.setter - def header(self, value): - self._header = value - api_args = {'monitor': {'header': self._header}} - uri = '/Failover/{}/{}/'.format(self.zone, self.fqdn) - DynectSession.get_session().execute(uri, 'PUT', api_args) - - @property - def expected(self): - """For HTTP(S) probes, a string to search for in the response. For - SMTP probes, a string to compare the banner against. Failure to find - this string means the monitor will report a down status - """ - return self._expected - @expected.setter - def expected(self, value): - self._expected = value - api_args = {'monitor': {'expected': self._expected}} - uri = '/Failover/{}/{}/'.format(self.zone, self.fqdn) - DynectSession.get_session().execute(uri, 'PUT', api_args) - - def __str__(self): - """str override""" - return force_unicode(': {}').format(self._protocol) - __repr__ = __unicode__ = __str__ - - def __bytes__(self): - """bytes override""" - return bytes(self.__str__()) - - -class GSLBRegionPoolEntry(object): - """:class:`GSLBRegionPoolEntry`""" def __init__(self, zone, fqdn, region_code, address, *args, **kwargs): """Create a :class:`GSLBRegionPoolEntry` object @@ -224,176 +47,58 @@ def __init__(self, zone, fqdn, region_code, address, *args, **kwargs): :param serve_mode: Sets the behavior of this particular record. Must be one of 'always', 'obey', 'remove', 'no' """ - super(GSLBRegionPoolEntry, self).__init__() - self.valid_serve_modes = ('always', 'obey', 'remove', 'no') - self.valid_weight = range(1, 15) - self._zone = zone - self._fqdn = fqdn - self._region_code = region_code - self._address = address - self._label = self._weight = self._serve_mode = None - uri = '/GSLBRegionPoolEntry/{}/{}/{}/{}/' - self.uri = uri.format(self._zone, self._fqdn, self._region_code, - self._address) - if len(args) == 0 and len(kwargs) == 0: - self._get() - else: - for key, val in kwargs.items(): - setattr(self, '_' + key, val) + self.zone, self.fqdn, = zone, fqdn + self.region_code, self.address = region_code, address + self.uri = self.uri.format(zone=zone, fqdn=fqdn, region=region_code, + address=address) + super(GSLBRegionPoolEntry, self).__init__(*args, **kwargs) def _post(self, label=None, weight=None, serve_mode=None): """Create a new :class:`GSLBRegionPoolEntry` on the DynECT System""" - self._label = label - self._weight = weight - self._serve_mode = serve_mode - uri = '/GSLBRegionPoolEntry/{}/{}/{}/'.format(self._zone, self._fqdn, - self._region_code) - api_args = {'address': self._address} - if label: - api_args['label'] = self._label - if weight: - if weight not in self.valid_weight: - raise DynectInvalidArgumentError('weight', weight, - self.valid_weight) - api_args['weight'] = self._weight - if serve_mode: - if serve_mode not in self.valid_serve_modes: - raise DynectInvalidArgumentError('serve_mode', serve_mode, - self.valid_serve_modes) - api_args['serve_mode'] = self._serve_mode - response = DynectSession.get_session().execute(uri, 'POST', api_args) - for key, val in response['data'].items(): - setattr(self, '_' + key, val) - - def _get(self): - """Get an existing :class:`GSLBRegionPoolEntry` object from the DynECT - System - """ - api_args = {} - response = DynectSession.get_session().execute(self.uri, 'GET', - api_args) - for key, val in response['data'].items(): - setattr(self, '_' + key, val) - - def _update(self, api_args): - """Private update method""" - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - for key, val in response['data'].items(): - setattr(self, '_' + key, val) - - def sync(self): - """Sync this :class:`GSLBRegionPoolEntry` object with the DynECT System - """ - self._get() - - @property - def zone(self): - """Zone monitored by this :class:`GSLBRegionPoolEntry`""" - return self._zone - @zone.setter - def zone(self, value): - pass - - @property - def fqdn(self): - """The fqdn of the specific node which will be monitored by this - :class:`GSLBRegionPoolEntry` - """ - return self._fqdn - @fqdn.setter - def fqdn(self, value): - pass - - @property - def region_code(self): - """ISO Region Code for this :class:`GSLBRegionPoolEntry`""" - return self._region_code - @region_code.setter - def region_code(self, value): - pass - - @property - def address(self): - """The IP address or FQDN of this Node IP""" - return self._address - @address.setter - def address(self, value): - self._address = value - api_args = {'new_address': self._address} - self._update(api_args) - - @property - def label(self): - """Identifying descriptive information for this - :class:`GSLBRegionPoolEntry` - """ - return self._label - @label.setter - def label(self, value): - self._label = value - api_args = {'label': self._label} - self._update(api_args) + uri = '/GSLBRegionPoolEntry/{0}/{1}/{2}/'.format(self.zone, self.fqdn, + self.region_code) + api_args = {'address': self.address, 'label': label, 'weight': weight, + 'serve_mode': serve_mode} + api_args = {api_args[k] for k in api_args if api_args[k] is not None} + response = DynectSession.post(uri, api_args) + self._build(response['data']) - @property - def weight(self): - """A number in the range of 1-14 controlling the order in which this - :class:`GSLBRegionPoolEntry` will be served. - """ - return self._weight - @weight.setter - def weight(self, new_weight): - if new_weight not in self.valid_weight: - raise DynectInvalidArgumentError(new_weight, - self.valid_weight) - self._weight = new_weight - api_args = {'weight': self._weight} - self._update(api_args) - - @property - def serve_mode(self): - """Sets the behavior of this particular record. Must be one of 'always', - 'obey', 'remove', or 'no' - """ - return self._serve_mode - @serve_mode.setter - def serve_mode(self, new_serve_mode): - if new_serve_mode not in self.valid_serve_modes: - raise DynectInvalidArgumentError('serve_mode', new_serve_mode, - self.valid_serve_modes) - self._serve_mode = new_serve_mode - api_args = {'serve_mode': self._serve_mode} - self._update(api_args) + def _update(self, **api_args): + if 'address' in api_args: + api_args['new_address'] = api_args.pop('address') + super(GSLBRegionPoolEntry, self)._update(**api_args) def to_json(self): """Convert this object into a json blob""" - output = {'address': self._address} - if self._label: - output['label'] = self._label - if self._weight: - output['weight'] = self._weight - if self._serve_mode: - output['serve_mode'] = self._serve_mode + output = {'address': self.address} + if self.label: + output['label'] = self.label + if self.weight: + output['weight'] = self.weight + if self.serve_mode: + output['serve_mode'] = self.serve_mode return output - def delete(self): - """Delete this :class:`GSLBRegionPoolEntry` from the DynECT System""" - api_args = {} - DynectSession.get_session().execute(self.uri, 'DELETE', api_args) - def __str__(self): - """str override""" - s = force_unicode(': {}') - return s.format(self._region_code) - __repr__ = __unicode__ = __str__ - - def __bytes__(self): - """bytes override""" - return bytes(self.__str__()) - + return force_unicode(': {0}'.format( + self.region_code) + ) + + +class GSLBRegion(DNSAPIObject): + uri = '/GSLBRegion/{zone}/{fqdn}/{region}/' + zone = ImmutableAttribute('zone') + fqdn = ImmutableAttribute('fqdn') + region_code = ImmutableAttribute('region_code') + pool = ListAttribute('pool') + serve_count = IntegerAttribute('serve_count') + failover_mode = ValidatedAttribute('failover_mode', + validator=('ip', 'cname', 'region', + 'global')) + failover_data = ValidatedAttribute('failover_data', + validator=('ip', 'cname', 'region', + 'global')) -class GSLBRegion(object): - """docstring for GSLBRegion""" def __init__(self, zone, fqdn, region_code, *args, **kwargs): """Create a :class:`GSLBRegion` object @@ -406,211 +111,100 @@ def __init__(self, zone, fqdn, region_code, *args, **kwargs): response :param failover_mode: How the :class:`GSLBRegion` should failover. Must be one of 'ip', 'cname', 'region', 'global' - :param failover_data: Dependent upon failover_mode. Must be one of 'ip', - 'cname', 'region', 'global' + :param failover_data: Dependent upon failover_mode. Must be one of + 'ip', 'cname', 'region', 'global' """ - super(GSLBRegion, self).__init__() - self._zone = zone - self._fqdn = fqdn - self._region_code = region_code - self._pool = self._serve_count = self._failover_mode = None - self._failover_data = None - self.uri = '/GSLBRegion/{}/{}/{}/'.format(self._zone, self._fqdn, - self._region_code) self._pool = [] - if len(args) == 0 and len(kwargs) == 0: - self._get() - if len(kwargs) > 0: - for key, val in kwargs.items(): - if key == 'pool': - for pool in val: - if isinstance(pool, dict): - self._pool.append(GSLBRegionPoolEntry(self._zone, - self._fqdn, - self._region_code, - **pool)) - else: - self._pool.append(pool) - else: - setattr(self, '_' + key, val) - elif len(args) > 0: - for pool in args[0]: - if isinstance(pool, dict): - self._pool.append(GSLBRegionPoolEntry(self._zone, - self._fqdn, - self._region_code, - **pool)) - else: - self._pool.append(pool) + self.uri = self.uri.format(zone=zone, fqdn=fqdn, region=region_code) + super(GSLBRegion, self).__init__(*args, **kwargs) - def _post(self, pool, serve_count=None, failover_mode=None, - failover_data=None): - """Create a new :class:`GSLBRegion` on the DynECT System""" - self._pool = pool - self._serve_count = serve_count - self._failover_mode = failover_mode - self._failover_data = failover_data - uri = '/GSLBRegion/{}/{}/'.format(self._zone, self._fqdn) - api_args = {'pool': self._pool.to_json()} - if serve_count: - api_args['serve_count'] = self._serve_count - if failover_mode: - api_args['failover_mode'] = self._failover_mode - if failover_data: - api_args['failover_data'] = self._failover_data - response = DynectSession.get_session()(uri, 'POST', api_args) - for key, val in response['data'].items(): - if key == 'pool': - for pool in val: - if isinstance(pool, dict): - self._pool.append(GSLBRegionPoolEntry(self._zone, - self._fqdn, - self._region_code, - **pool)) - else: - setattr(self, '_' + key, val) - - def _get(self): - """Get an existing :class:`GSLBRegion` object""" - api_args = {} - response = DynectSession.get_session().execute(self.uri, 'GET', - api_args) - for key, val in response['data'].items(): - if key == 'pool': - for pool in val: - if isinstance(pool, dict): - self._pool.append(GSLBRegionPoolEntry(self._zone, - self._fqdn, - self._region_code, - method=None, - **pool)) - else: - self._pool.append(pool) - else: - setattr(self, '_' + key, val) - - def _update(self, api_args): - """Private udpate method for PUT commands""" - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - for key, val in response['data'].items(): - if key == 'pool': - for pool in val: - if isinstance(pool, dict): - self._pool.append(GSLBRegionPoolEntry(self._zone, - self._fqdn, - self._region_code, - method=None, - **pool)) - else: - self._pool.append(pool) - else: - setattr(self, '_' + key, val) - - def sync(self): - """Sync this :class:`GSLBRegion` object with the DynECT System""" - self._get() - - @property - def zone(self): - """Zone monitored by this :class:`GSLBRegion`""" - return self._zone - @zone.setter - def zone(self, value): - pass + def add_entry(self, address, label=None, weight=None, serve_mode=None): + """Create a new :class:`GSLBRegionPoolEntry` with the provided + parameters and add it to this :class:`GSLBRegion` - @property - def fqdn(self): - """The fqdn of the specific node which will be monitored by this - :class:`GSLBRegion` + :param address: The IP address or FQDN of this Node IP + :param label: Identifying descriptive information for this + :param weight: A number in the range of 1-14 controlling the order in + which this :class:`GSLBRegionPoolEntry` will be served + :param serve_mode: Sets the behavior of this particular record. Must be + one of 'always', 'obey', 'remove', 'no' + :return: The newly created :class:`GSLBRegionPoolEntry` """ - return self._fqdn - @fqdn.setter - def fqdn(self, value): - pass - - @property - def region_code(self): - """ISO region code of this :class:`GSLBRegion`""" - return self._region_code - @region_code.setter - def region_code(self, value): - pass + new_entry = GSLBRegionPoolEntry(self.zone, self.fqdn, self.region_code, + address, label, weight, serve_mode) + self._pool.append(new_entry) + return new_entry - @property - def serve_count(self): - """How many records will be returned in each DNS response""" - return self._serve_count - @serve_count.setter - def serve_count(self, value): - self._serve_count = value - api_args = {'serve_count': self._serve_count} - self._update(api_args) + def _post(self, pool, serve_count=None, failover_mode=None, + failover_data=None): + """Create a new :class:`GSLBRegion` on the DynECT System""" + uri = '/GSLBRegion/{0}/{1}/'.format(self.zone, self.fqdn) + api_args = {'pool': pool.to_json(), 'serve_count': serve_count, + 'failover_mode': failover_mode, + 'failover_data': failover_data} + api_args = {api_args[k] for k in api_args if api_args[k] is not None} + response = DynectSession.post(uri, api_args) + self._build(response['data']) - @property - def failover_mode(self): - """How the :class:`GSLBRegion` should failover. Must be one of 'ip', - 'cname', 'region', 'global' - """ - return self._failover_mode - @failover_mode.setter - def failover_mode(self, value): - self._failover_mode = value - api_args = {'failover_mode': self._failover_mode} - self._update(api_args) + def _build(self, data): + if 'pool' in data: + pools = data.pop('pool') + for pool in pools: + if isinstance(pool, dict): + self._pool.append(GSLBRegionPoolEntry(self.zone, self.fqdn, + self.region_code, + method=None, **pool)) + else: + self._pool.append(pool) + super(GSLBRegion, self)._build(data) - @property - def failover_data(self): - """Dependent upon failover_mode. Must be one of 'ip', 'cname', 'region', - 'global' - """ - return self._failover_data - @failover_data.setter - def failover_data(self, value): - self._failover_data = value - api_args = {'failover_data': self._failover_data} - self._update(api_args) + def _update(self, **api_args): + if 'pool' in api_args: + api_args['pool'] = api_args['pool'].to_json() + super(GSLBRegion, self)._update(**api_args) @property - def pool(self): - """The IP Pool list for this :class:`GSLBRegion`""" - return self._pool - @pool.setter - def pool(self, value): - self._pool = value - api_args = {'pool': self._pool.to_json()} - self._update(api_args) - - @property - def _json(self): + def to_json(self): """Convert this :class:`GSLBRegion` to a json blob""" output = {'region_code': self.region_code, 'pool': [pool.to_json() for pool in self._pool]} - if self._serve_count: - output['serve_count'] = self._serve_count - if self._failover_mode: - output['failover_mode'] = self._failover_mode - if self._failover_data: - output['failover_data'] = self._failover_data + if self.serve_count: + output['serve_count'] = self.serve_count + if self.failover_mode: + output['failover_mode'] = self.failover_mode + if self.failover_data: + output['failover_data'] = self.failover_data return output - def delete(self): - """Delete this :class:`GSLBRegion`""" - api_args = {} - DynectSession.get_session().execute(self.uri, 'DELETE', api_args) - def __str__(self): - """str override""" - return force_unicode(': {}').format(self._region_code) - __repr__ = __unicode__ = __str__ + return force_unicode(': {0}').format(self.region_code) - def __bytes__(self): - """bytes override""" - return bytes(self.__str__()) - -class GSLB(object): +class GSLB(APIService, DNSAPIObject): """A Global Server Load Balancing (GSLB) service""" + uri = '/GSLB/{zone}/{fqdn}/' + zone = ImmutableAttribute('zone') + fqdn = ImmutableAttribute('fqdn') + auto_recover = ValidatedAttribute('auto_recover', validator=('Y', 'N')) + ttl = ValidatedAttribute('ttl', validator=(30, 60, 150, 300, 450)) + notify_events = ValidatedAttribute('notify_events', + validator=('ip', 'svc', 'nosrv')) + syslog_server = StringAttribute('syslog_server') + syslog_port = IntegerAttribute('syslog_port') + syslog_ident = StringAttribute('syslog_ident') + syslog_facility = ValidatedAttribute('syslog_facility', + validator=( + 'kern', 'user', 'mail', 'daemon', + 'auth', 'syslog', 'lpr', 'news', + 'uucp', 'cron', 'authpriv', 'ftp', + 'ntp', 'security', 'console', + 'local0', 'local1', 'local2', + 'local3', 'local4', 'local5', + 'local6', 'local7' + )) + monitor = ClassAttribute('monitor', class_type=GSLBMonitor) + contact_nickname = StringAttribute('contact_nickname') + def __init__(self, zone, fqdn, *args, **kwargs): """Create a :class:`GSLB` object @@ -619,10 +213,10 @@ def __init__(self, zone, fqdn, *args, **kwargs): active status or if the service should remain in failover until manually reset. Must be 'Y' or 'N' :param ttl: Time To Live in seconds of records in the service. Must be - less than 1/2 of the Health Probe's monitoring interval. Must be one - of 30, 60, 150, 300, or 450 - :param notify_events: A comma separated list of the events which trigger - notifications. Must be one of 'ip', 'svc', or 'nosrv' + less than 1/2 of the Health Probe's monitoring interval. Must be + one of 30, 60, 150, 300, or 450 + :param notify_events: A comma separated list of the events which + trigger notifications. Must be one of 'ip', 'svc', or 'nosrv' :param syslog_server: The Hostname or IP address of a server to receive syslog notifications on monitoring events :param syslog_port: The port where the remote syslog server listens for @@ -637,329 +231,99 @@ def __init__(self, zone, fqdn, *args, **kwargs): :param monitor: The health :class:`Monitor` for this service :param contact_nickname: Name of contact to receive notifications """ - super(GSLB, self).__init__() - self.valid_auto_recover = ('Y', 'N') - self.valid_ttls = (30, 60, 150, 300, 450) - self.valid_notify_events = ('ip', 'svc', 'nosrv') - self.valid_syslog_facility = ('kern', 'user', 'mail', 'daemon', 'auth', - 'syslog', 'lpr', 'news', 'uucp', 'cron', - 'authpriv', 'ftp', 'ntp', 'security', - 'console', 'local0', 'local1', 'local2', - 'local3', 'local4', 'local5', 'local6', - 'local7') - self._zone = zone - self._fqdn = fqdn - self.uri = '/GSLB/{}/{}/'.format(self._zone, self._fqdn) - self._auto_recover = self._ttl = self._notify_events = None - self._syslog_server = self._syslog_port = self._syslog_ident = None - self._syslog_facility = self._monitor = self._contact_nickname = None + self.uri = self.uri.format(zone=zone, fqdn=fqdn) + + super(GSLB, self).__init__(*args, **kwargs) + self._active = self._status = self.active = None self._region = APIList(DynectSession.get_session, 'region') - if 'api' in kwargs: - del kwargs['api'] - self._build(kwargs) - elif len(args) == 0 and len(kwargs) == 0: - self._get() - else: - self._post(*args, **kwargs) + self._region.uri = self.uri def _post(self, contact_nickname, region, auto_recover=None, ttl=None, notify_events=None, syslog_server=None, syslog_port=514, syslog_ident='dynect', syslog_facility='daemon', monitor=None): """Create a new :class:`GSLB` service object on the DynECT System""" - self._auto_recover = auto_recover - self._ttl = ttl - self._notify_events = notify_events - self._syslog_server = syslog_server - self._syslog_port = syslog_port - self._syslog_ident = syslog_ident - self._syslog_facility = syslog_facility - self._region += region - self._monitor = monitor - self._contact_nickname = contact_nickname - api_args = {'contact_nickname': self._contact_nickname, - 'region': [r._json for r in self._region]} - if auto_recover: - api_args['auto_recover'] = self._auto_recover - if ttl: - api_args['ttl'] = self._ttl - if notify_events: - api_args['notify_events'] = self._notify_events - if syslog_server: - api_args['syslog_server'] = self._syslog_server - if syslog_port: - api_args['syslog_port'] = self._syslog_port - if syslog_ident: - api_args['syslog_ident'] = self._syslog_ident - if syslog_facility: - api_args['syslog_facility'] = self._syslog_facility - if monitor: - api_args['monitor'] = self._monitor.to_json() - self._monitor.zone = self._zone - self._monitor.fqdn = self._fqdn - response = DynectSession.get_session().execute(self.uri, 'POST', - api_args) + api_args = {'contact_nickname': contact_nickname, + 'region': [r.to_json() for r in region], + 'auto_recover': auto_recover, 'ttl': ttl, + 'notify_events': notify_events, + 'syslog_server': syslog_server, 'syslog_port': syslog_port, + 'syslog_ident': syslog_ident, + 'syslog_facility': syslog_facility, + 'monitor': monitor.to_json()} + api_args = {api_args[k] for k in api_args if api_args[k] is not None} + response = DynectSession.post(self.uri, api_args) self._build(response['data']) - def _get(self): - """Get an existing :class:`GSLB` service object""" - api_args = {} - response = DynectSession.get_session().execute(self.uri, 'GET', - api_args) - self._build(response['data']) - - def _build(self, data, region=True): + def _build(self, data): """Private method which builds the objects fields based on the data returned by an API call :param data: the data from the JSON respnose - :param region: Boolean flag specifying whether to rebuild the region - objects or not """ - for key, val in data.items(): - if key == 'region': - if region: - self._region = APIList(DynectSession.get_session, 'region') - for region in val: - region_code = region.pop('region_code', None) - self._region.append(GSLBRegion(self._zone, self._fqdn, - region_code, **region)) - elif key == 'monitor': + if 'region' in data: + self._region = APIList(DynectSession.get_session, 'region') + for region in data.pop('region'): + region_code = region.pop('region_code', None) + self._region.append(GSLBRegion(self.zone, self.fqdn, + region_code, **region)) + self._region.uri = self.uri + if 'monitor' in data: + if getattr(self, '_monitor', None) is not None: # We already have the monitor object, no need to rebuild it - pass + data.pop('monitor') else: - setattr(self, '_' + key, val) - self._region.uri = self.uri - - def sync(self): - """Sync this :class:`GSLB` object with the DynECT System""" - self._get() - - def activate(self): - """Activate this :class:`GSLB` service on the DynECT System""" - api_args = {'activate': True} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data'], region=False) - - def deactivate(self): - """Deactivate this :class:`GSLB` service on the DynECT System""" - api_args = {'deactivate': True} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data'], region=False) + self._monitor = data.pop('monitor') + self._monitor.zone = self.zone + self._monitor.fqdn = self.fqdn + super(GSLB, self)._build(data) + + def _update(self, **api_args): + if 'region' in api_args: + region = api_args.pop('region') + if isinstance(region, list) and not isinstance(region, APIList): + self._region = APIList(DynectSession.get_session, 'region', + None, region) + elif isinstance(region, APIList): + self._region = region + self._region.uri = self.uri + if 'monitor' in api_args: + monitor = api_args.pop('monitor') + # We're only going accept new monitors of type Monitor + if isinstance(monitor, GSLBMonitor): + api_args['monitor'] = monitor.to_json() + super(GSLB, self)._update(**api_args) def recover(self, address=None): """Recover the GSLB service on the designated zone node or a specific node IP within the service """ - api_args = {} if address: - api_args['recoverip'] = True - api_args['address'] = address + api_args = {'recoverip': True, 'address': address} else: - api_args['recover'] = True - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data'], region=False) - - @property - def auto_recover(self): - """Indicates whether or not the service should automatically come out of - failover when the IP addresses resume active status or if the service - should remain in failover until manually reset. Must be 'Y' or 'N' - """ - return self._auto_recover - @auto_recover.setter - def auto_recover(self, value): - if value not in self.valid_auto_recover: - raise DynectInvalidArgumentError('auto_recover', value, - self.valid_auto_recover) - self._auto_recover = value - api_args = {'auto_recover': self._auto_recover} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data'], region=False) - - @property - def status(self): - """The current state of the service. Will be one of 'unk', 'ok', - 'trouble', or 'failover' - """ - api_args = {} - respnose = DynectSession.get_session().execute(self.uri, 'GET', - api_args) - self._status = respnose['data']['status'] - return self._status - @status.setter - def status(self, value): - pass - - @property - def active(self): - """Indicates if the service is active. When setting directly, rather - than using activate/deactivate valid arguments are 'Y' or True to - activate, or 'N' or False to deactivate. Note: If your service is - already active and you try to activate it, nothing will happen. And - vice versa for deactivation. - - :returns: An :class:`Active` object representing the current state of - this :class:`GSLB` Service - """ - return self._active - @active.setter - def active(self, value): - deactivate = ('N', False) - activate = ('Y', True) - if value in deactivate and self.active: - self.deactivate() - elif value in activate and not self.active: - self.activate() - - @property - def ttl(self): - """Time To Live in seconds of records in the service. Must be less than - 1/2 of the Health Probe's monitoring interval. Must be one of 30, 60, - 150, 300, or 450 - """ - return self._ttl - @ttl.setter - def ttl(self, value): - if value not in self.valid_ttls: - raise DynectInvalidArgumentError('ttl', value, self.valid_ttls) - self._ttl = value - api_args = {'ttl': self._ttl} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data'], region=False) - - @property - def notify_events(self): - """A comma separated list of the events which trigger notifications. - Must be one of 'ip', 'svc', or 'nosrv' - """ - return self._notify_events - @notify_events.setter - def notify_events(self, value): - if value not in self.valid_notify_events: - raise DynectInvalidArgumentError('notify_events', value, - self.valid_notify_events) - self._notify_events = value - api_args = {'notify_events': self._notify_events} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data'], region=False) - - @property - def syslog_server(self): - """The Hostname or IP address of a server to receive syslog - notifications on monitoring events - """ - return self._syslog_server - @syslog_server.setter - def syslog_server(self, value): - self._syslog_server = value - api_args = {'syslog_server': self._syslog_server} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data'], region=False) - - @property - def syslog_port(self): - """The port where the remote syslog server listens for notifications""" - return self._syslog_port - @syslog_port.setter - def syslog_port(self, value): - self._syslog_port = value - api_args = {'syslog_port': self._syslog_port} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data'], region=False) - - @property - def syslog_ident(self): - """The ident to use when sending syslog notifications""" - return self._syslog_ident - @syslog_ident.setter - def syslog_ident(self, value): - self._syslog_ident = value - api_args = {'syslog_ident': self._syslog_ident} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data'], region=False) - - @property - def syslog_facility(self): - """The syslog facility to use when sending syslog notifications. Must be - one of 'kern', 'user', 'mail', 'daemon', 'auth', 'syslog', 'lpr', - 'news', 'uucp', 'cron', 'authpriv', 'ftp', 'ntp', 'security', 'console', - 'local0', 'local1', 'local2', 'local3', 'local4', 'local5', 'local6', or - 'local7' - """ - return self._syslog_facility - @syslog_facility.setter - def syslog_facility(self, value): - if value not in self.valid_syslog_facility: - raise DynectInvalidArgumentError('syslog_facility', value, - self.valid_syslog_facility) - self._syslog_facility = value - api_args = {'syslog_facility': self._syslog_facility} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data'], region=False) - - @property - def region(self): - """A list of :class:`GSLBRegion`'s""" - return self._region - @region.setter - def region(self, value): - if isinstance(value, list) and not isinstance(value, APIList): - self._region = APIList(DynectSession.get_session, 'region', None, - value) - elif isinstance(value, APIList): - self._region = value - self._region.uri = self.uri + api_args = {'recover': True} + response = DynectSession.put(self.uri, api_args) + self._build(response['data']) - @property - def monitor(self): - """The health :class:`Monitor` for this service""" - return self._monitor - @monitor.setter - def monitor(self, value): - # We're only going accept new monitors of type Monitor - if isinstance(value, Monitor): - api_args = {'monitor': value.to_json()} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data'], region=False) - self._monitor = value + def add_region(self, region_code, pool, serve_count=None, + failover_mode=None, failover_data=None): + """Add a new :class:`GSLBRegion` to this :class:`GSLB` service - @property - def contact_nickname(self): - """Name of contact to receive notifications from this :class:`GSLB` - service + :param region_code: ISO region code of this :class:`GSLBRegion` + :param pool: The IP Pool list for this :class:`GSLBRegion` + :param serve_count: How many records will be returned in each DNS + response + :param failover_mode: How the :class:`GSLBRegion` should failover. Must + be one of 'ip', 'cname', 'region', 'global' + :param failover_data: Dependent upon failover_mode. Must be one of + 'ip', 'cname', 'region', 'global' + :return: The newly created :class:`GSLBRegion` object """ - return self._contact_nickname - @contact_nickname.setter - def contact_nickname(self, value): - self._contact_nickname = value - api_args = {'contact_nickname': self._contact_nickname} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data'], region=False) - - def delete(self): - """Delete this :class:`GSLB` service from the DynECT System""" - api_args = {} - DynectSession.get_session().execute(self.uri, 'DELETE', api_args) + new_region = GSLBRegion(self.zone, self.fqdn, region_code, pool, + serve_count, failover_mode, failover_data) + self._region.append(new_region) + return new_region def __str__(self): - """str override""" - return force_unicode(': {}').format(self._fqdn) - __repr__ = __unicode__ = __str__ - - def __bytes__(self): - """bytes override""" - return bytes(self.__str__()) + return force_unicode(': {0}').format(self.fqdn) diff --git a/dyn/tm/services/httpredirect.py b/dyn/tm/services/httpredirect.py index 4f64bad..f1e1ff3 100644 --- a/dyn/tm/services/httpredirect.py +++ b/dyn/tm/services/httpredirect.py @@ -1,136 +1,68 @@ # -*- coding: utf-8 -*- -"""This module contains API Wrapper implementations of the HTTP Redirect service +"""This module contains API Wrapper implementations of the HTTP Redirect +service """ -import logging - -from ..session import DynectSession +from ...core import (ImmutableAttribute, ValidatedAttribute, StringAttribute, + BooleanAttribute) +from ..session import DynectSession, DNSAPIObject from ...compat import force_unicode __author__ = 'xorg' __all__ = ['HTTPRedirect'] -class HTTPRedirect(object): +class HTTPRedirect(DNSAPIObject): """HTTPRedirect is a service which sets up a redirect to the specified URL.// """ + uri = '/HTTPRedirect/{zone}/{fqdn}/' + + #: The zone that this HTTPRedirect Service is attached to + zone = ImmutableAttribute('zone') + + #: The FQDN of the node where this service is attached + fqdn = ImmutableAttribute('fqdn') + + #: HTTP response code to return for redirection + code = ValidatedAttribute('code', default=302, validator=(301, 302)) + + #: The target URL where the client is sent. Must begin with either http:// + #: or https:// + url = StringAttribute('url') + + #: A flag indicating whether the redirection should include the originally + #: requested URI. + keep_uri = BooleanAttribute('keep_uri', default=True) + def __init__(self, zone, fqdn, *args, **kwargs): """Create a new :class:`HTTPRedirect` service object :param zone: The zone to attach this HTTPRedirect Service to :param fqdn: The FQDN of the node where this service will be attached :param code: HTTP response code to return for redirection. - :param url: The target URL where the client is sent. Must begin with either http:// or https:// - :param keep_uri: A flag indicating whether the redirection should include the originally requested URI. + :param url: The target URL where the client is sent. Must begin with + either http:// or https:// + :param keep_uri: A flag indicating whether the redirection should + include the originally requested URI """ - super(HTTPRedirect, self).__init__() - self._zone = zone - self._fqdn = fqdn - self._code = self._url = self.keep_uri = None - if 'api' in kwargs: - del kwargs['api'] - for key, val in kwargs.items(): - setattr(self, '_' + key, val) - elif len(args) + len(kwargs) == 1: - self._get() - else: - self._post(*args, **kwargs) + self._zone, self._fqdn = zone, fqdn + self.uri = self.uri.format(zone=zone, fqdn=fqdn) + super(HTTPRedirect, self).__init__(*args, **kwargs) def _get(self): """Build an object around an existing DynECT HTTPRedirect Service""" - self.uri = '/HTTPRedirect/{}/{}/'.format(self._zone, self._fqdn) api_args = {'detail': 'Y'} - response = DynectSession.get_session().execute(self.uri, 'GET', - api_args) - for key, val in response['data'].items(): - setattr(self, '_' + key, val) + response = DynectSession.get(self.uri, api_args) + self._build(response['data']) - def _post(self, code, keep_uri, url): + def _post(self, url, code=302, keep_uri=True): """Create a new HTTPRedirect Service on the DynECT System""" - self._code = code - self._keep_uri = keep_uri - self._url = url - self.uri = '/HTTPRedirect/{}/{}/'.format(self._zone, self._fqdn) - api_args = {'code': self._code, 'keep_uri': self._keep_uri, 'url': self._url} - response = DynectSession.get_session().execute(self.uri, 'POST', - api_args) - for key, val in response['data'].items(): - setattr(self, '_' + key, val) - - def _update(self, code, keep_uri, url): - """Update an existing HTTPRedirect Service on the DynECT System""" - self._code = code - self._keep_uri = keep_uri - self._url = url - self.uri = '/HTTPRedirect/{}/{}/'.format(self._zone, self._fqdn) - api_args = {'code': self._code, 'keep_uri': self._keep_uri, 'url': self._url} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - for key, val in response['data'].items(): - setattr(self, '_' + key, val) - - - - @property - def zone(self): - """The zone that this HTTPRedirect Service is attached to is a read-only - attribute - """ - return self._zone - @zone.setter - def zone(self, value): - pass - - @property - def fqdn(self): - """The fqdn that this HTTPRedirect Service is attached to is a read-only - attribute - """ - return self._fqdn - @fqdn.setter - def fqdn(self, value): - pass - - @property - def code(self): - """HTTP response code to return for redirection. - Valid values: - 301 – Permanent redirect - 302 – Temporary redirect - """ - return self._code - @code.setter - def code(self, value): - pass - - @property - def keep_uri(self): - """A flag indicating whether the redirection should include the originally requested URI. - Valid values: Y, N - """ - return self._keep_uri - @keep_uri.setter - def keep_uri(self, value): - pass - - @property - def url(self): - """The target URL where the client is sent. Must begin with either http:// or https://""" - return self._url - @url.setter - def url(self, value): - pass - - - def delete(self): - """Delete this HTTPRedirect service from the DynECT System""" - api_args = {} - DynectSession.get_session().execute(self.uri, 'DELETE', api_args) + self._url, self._code, self._keep_uri = url, code, keep_uri + api_args = {'code': self._code, + 'keep_uri': self._keep_uri, + 'url': self._url} + response = DynectSession.post(self.uri, api_args) + self._build(response['data']) def __str__(self): """str override""" - return force_unicode(': {}').format(self._fqdn) - __repr__ = __unicode__ = __str__ - - def __bytes__(self): - """bytes override""" - return bytes(self.__str__()) + return force_unicode(': {0}').format(self._fqdn) diff --git a/dyn/tm/services/reversedns.py b/dyn/tm/services/reversedns.py index 7ccaf55..76f29c8 100644 --- a/dyn/tm/services/reversedns.py +++ b/dyn/tm/services/reversedns.py @@ -1,16 +1,29 @@ # -*- coding: utf-8 -*- -import logging - from ..utils import Active -from ..session import DynectSession +from ..session import DynectSession, DNSAPIObject +from ...core import (APIService, StringAttribute, ImmutableAttribute, + ListAttribute, ValidatedListAttribute, IntegerAttribute) from ...compat import force_unicode __author__ = 'jnappi' __all__ = ['ReverseDNS'] -class ReverseDNS(object): +class ReverseDNS(APIService, DNSAPIObject): """A DynECT ReverseDNS service""" + uri = '/IPTrack/{zone}/{fqdn}/' + _get_length = 1 + + zone = ImmutableAttribute('zone') + fqdn = ImmutableAttribute('fqdn') + hosts = ListAttribute('hosts') + netmask = StringAttribute('netmask') + ttl = IntegerAttribute('ttl') + record_types = ValidatedListAttribute('record_types', + validator=('A', 'AAAA', 'DynA', + 'DynAAAA')) + iptrack_id = StringAttribute('iptrack_id') + def __init__(self, zone, fqdn, *args, **kwargs): """Create an new :class:`ReverseDNS` object instance @@ -26,188 +39,41 @@ def __init__(self, zone, fqdn, *args, **kwargs): track. Note: Both A and AAAA can not be monitored by the same service """ - super(ReverseDNS, self).__init__() - self._zone = zone - self._fqdn = fqdn - self.valid_record_types = ('A', 'AAAA', 'DynA', 'DynAAAA') - self._hosts = self._netmask = self._ttl = self._record_types = None - self._iptrack_id = self.uri = self._active = None - if 'api' in kwargs: - del kwargs['api'] - for key, val in kwargs.items(): - setattr(self, '_' + key, val) - elif len(args) + len(kwargs) == 1: - self._get(*args, **kwargs) - else: - self._post(*args, **kwargs) + self.uri = self.uri.format(zone=zone, fqdn=fqdn) + super(ReverseDNS, self).__init__(*args, **kwargs) def _post(self, hosts, netmask, ttl='default', record_types=None): """Create a new ReverseDNS Service on the DynECT System""" - self._hosts = hosts - self._netmask = netmask - self._ttl = ttl - self._record_types = [] - if record_types is None: - record_types = ['A'] - for record_type in record_types: - if record_type in self.valid_record_types: - self._record_types.append(record_type) - api_args = {'record_types': self._record_types, - 'hosts': self._hosts, - 'netmask': self._netmask} + record_types = ['A'] if record_types is None else record_types + self.record_types = record_types # Validation check + api_args = {'record_types': self.record_types, 'hosts': hosts, + 'netmask': netmask} if ttl is not None: - api_args['ttl'] = self._ttl - uri = '/IPTrack/{}/{}/'.format(self._zone, self._fqdn) - response = DynectSession.get_session().execute(uri, 'POST', api_args) + api_args['ttl'] = ttl + response = DynectSession.post(self.uri, api_args) self._build(response['data']) - self.uri = '/IPTrack/{}/{}/{}/'.format(self._zone, self._fqdn, - self._iptrack_id) def _get(self, service_id): """Build an object around an existing DynECT ReverseDNS Service""" - self._iptrack_id = service_id - uri = '/IPTrack/{}/{}/{}/'.format(self._zone, self._fqdn, - self._iptrack_id) - api_args = {} - response = DynectSession.get_session().execute(uri, 'GET', api_args) - self._build(response['data']) - self.uri = '/IPTrack/{}/{}/{}/'.format(self._zone, self._fqdn, - self._iptrack_id) - - def _update(self, api_args): - """Update this object by making a PUT API call with the provided - api_args - """ - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) + uri = '/IPTrack/{0}/{1}/{2}/'.format(self.zone, self.fqdn, service_id) + response = DynectSession.get(uri) self._build(response['data']) def _build(self, data): """Build this object based on the data contained in an API response""" - for key, val in data.items(): - if key == 'active': - self._active = Active(val) - else: - setattr(self, '_' + key, val) - - @property - def zone(self): - """The zone that this ReverseDNS Service is attached to is a read-only - attribute - """ - return self._zone - @zone.setter - def zone(self, value): - pass - - @property - def fqdn(self): - """The fqdn that this ReverseDNS Service is attached to is a read-only - attribute - """ - return self._fqdn - @fqdn.setter - def fqdn(self, value): - pass - - @property - def active(self): - """Indicates whether or not the service is active. When setting - directly, rather than using activate/deactivate valid arguments are 'Y' - or True to activate, or 'N' or False to deactivate. Note: If your - service is already active and you try to activate it, nothing will - happen. And vice versa for deactivation. - - :returns: An :class:`Active` object representing the current state of - this :class:`ReverseDNS` Service - """ - return self._active - @active.setter - def active(self, value): - deactivate = ('N', False) - activate = ('Y', True) - if value in deactivate and self.active: - self.deactivate() - elif value in activate and not self.active: - self.activate() - - @property - def iptrack_id(self): - """The unique System id for this service. This is a read-only property. - """ - return self._iptrack_id - @iptrack_id.setter - def iptrack_id(self, value): - pass - - @property - def record_types(self): - """Types of records to track""" - return self._record_types - @record_types.setter - def record_types(self, value): - self._record_types = value - api_args = {'record_types': self._record_types, - 'hosts': self._hosts} - self._update(api_args) - - @property - def hosts(self): - """Hostnames of zones in your account where you want to track records""" - return self._hosts - @hosts.setter - def hosts(self, value): - self._hosts = value - api_args = {'record_types': self._record_types, - 'hosts': self._hosts} - self._update(api_args) - - @property - def ttl(self): - """TTL for the created PTR records. Omit to use zone default""" - return int(self._ttl) - @ttl.setter - def ttl(self, value): - self._ttl = value - api_args = {'record_types': self._record_types, - 'hosts': self._hosts, - 'ttl': self._ttl} - self._update(api_args) - - @property - def netmask(self): - """A netmask to match A/AAAA rdata against. Matched records will get - PTR records, any others won't - """ - return self._netmask - @netmask.setter - def netmask(self, value): - self._netmask = value - api_args = {'record_types': self._record_types, - 'hosts': self._hosts, - 'netmask': self._netmask} - self._update(api_args) - - def activate(self): - """Activate this ReverseDNS service""" - api_args = {'activate': True} - self._update(api_args) - - def deactivate(self): - """Deactivate this ReverseDNS service""" - api_args = {'deactivate': True} - self._update(api_args) - - def delete(self): - """Delete this ReverseDNS service from the DynECT System""" - api_args = {} - DynectSession.get_session().execute(self.uri, 'DELETE', api_args) + if 'active' in data: + self._active = Active(data.pop('active')) + super(ReverseDNS, self)._build(data) + self.uri += '{0}/'.format(self.iptrack_id) + + def _update(self, **api_args): + special_cases = 'record_types', 'hosts', 'ttl', 'netmask' + for key in api_args: + if key in special_cases: + api_args['record_types'] = self.record_types + api_args['hosts'] = self.hosts + break # Only need to check/assign them once + super(ReverseDNS, self)._update(**api_args) def __str__(self): - """str override""" - return force_unicode(': {}').format(self._fqdn) - __repr__ = __unicode__ = __str__ - - def __bytes__(self): - """bytes override""" - return bytes(self.__str__()) + return force_unicode(': {0}').format(self.fqdn) diff --git a/dyn/tm/services/rttm.py b/dyn/tm/services/rttm.py index 0e50e05..4682b18 100644 --- a/dyn/tm/services/rttm.py +++ b/dyn/tm/services/rttm.py @@ -1,257 +1,54 @@ # -*- coding: utf-8 -*- -import logging from datetime import datetime +from ._shared import BaseMonitor from ..utils import APIList, Active, unix_date -from ..errors import DynectInvalidArgumentError -from ..session import DynectSession +from ..session import DynectSession, DNSAPIObject +from ...core import (APIService, ImmutableAttribute, StringAttribute, + ValidatedAttribute, IntegerAttribute, ClassAttribute, + ValidatedListAttribute, ListAttribute) from ...compat import force_unicode __author__ = 'jnappi' -__all__ = ['Monitor', 'PerformanceMonitor', 'RegionPoolEntry', 'RTTMRegion', - 'RTTM'] +__all__ = ['RTTMMonitor', 'RTTMPerformanceMonitor', 'RegionPoolEntry', + 'RTTMRegion', 'RTTM'] -class Monitor(object): - """A :class:`Monitor` for RTTM Service. May be used as a HealthMonitor""" - def __init__(self, protocol, interval, retries=None, timeout=None, - port=None, path=None, host=None, header=None, expected=None): - """Create a :class:`Monitor` object - - :param protocol: The protocol to monitor. Must be either HTTP, HTTPS, - PING, SMTP, or TCP - :param interval: How often (in minutes) to run the monitor. Must be 1, - 5, 10, or 15, - :param retries: The number of retries the monitor attempts on failure - before giving up - :param timeout: The amount of time in seconds before the connection - attempt times out - :param port: For HTTP(S)/SMTP/TCP probes, an alternate connection port - :param path: For HTTP(S) probes, a specific path to request - :param host: For HTTP(S) probes, a value to pass in to the Host - :param header: For HTTP(S) probes, additional header fields/values to - pass in, separated by a newline character. - :param expected: For HTTP(S) probes, a string to search for in the - response. For SMTP probes, a string to compare the banner against. - Failure to find this string means the monitor will report a down - status. - """ - super(Monitor, self).__init__() - self._protocol = protocol - self._interval = interval - self._retries = retries - self._timeout = timeout - self._port = port - self._path = path - self._host = host - self._header = header - self._expected = expected - self.zone = self.fqdn = self._status = None - self.valid_protocols = ('HTTP', 'HTTPS', 'PING', 'SMTP', 'TCP') - self.valid_intervals = (1, 5, 10, 15) - self.valid_timeouts = (10, 15, 25, 30) - - def to_json(self): - """Convert this :class:`HealthMonitor` object to a JSON blob""" - json_blob = {'protocol': self._protocol, - 'interval': self._interval} - for key, val in self.__dict__.items(): - if val is not None and not hasattr(val, '__call__') and \ - key.startswith('_'): - json_blob[key[1:]] = val - return json_blob - - def __eq__(self, other): - """eq override for comparing :class:`HealthMonitor` objects to JSON - response hashes or other :class:`DNSSECKey` instances - - :param other: the value to compare this :class:`HealthMonitor` to. Valid - input types: `dict`, :class:`HealthMonitor` - """ - if isinstance(other, dict): - return False - elif isinstance(other, Monitor): - return False - return False - - def _get(self): - """Update this :class:`Monitor` with data from the Dyn System""" - uri = '/RTTM/{}/{}/'.format(self.zone, self.fqdn) - api_args = {} - response = DynectSession.get_session().execute(uri, 'GET', api_args) - self._build(response['data']['monitor']) - - def _update(self, api_args): - """Update the Dyn System with data from this :class:`Monitor`""" - uri = '/RTTM/{}/{}/'.format(self.zone, self.fqdn) - response = DynectSession.get_session().execute(uri, 'PUT', api_args) - self._build(response['data']['monitor']) - - def _build(self, data): - """Update the fields in this :class:`PerformanceMonitor` with the data - from API calls. - - :param data: The 'data' field of API responses - """ - for key, val in data.items(): - setattr(self, '_' + key, val) - - @property - def status(self): - """Get the current status of this :class:`HealthMonitor` from the DynECT - System - """ - self._get() - return self._status - - @property - def protocol(self): - """The protocol to monitor""" - return self._protocol - @protocol.setter - def protocol(self, value): - if value not in self.valid_protocols: - raise Exception - self._protocol = value - api_args = {'monitor': {'protocol': self._protocol}} - self._update(api_args) - - @property - def interval(self): - """How often to run this monitor""" - return self._interval - @interval.setter - def interval(self, value): - if value not in self.valid_intervals: - raise Exception - self._interval = value - api_args = {'monitor': {'interval': self._interval}} - self._update(api_args) - - @property - def retries(self): - """The number of retries the monitor attempts on failure before giving - up - """ - return self._retries - @retries.setter - def retries(self, value): - self._retries = value - api_args = {'monitor': {'retries': self._retries}} - self._update(api_args) - - @property - def timeout(self): - """The amount of time in seconds before the connection attempt times - out - """ - return self._timeout - @timeout.setter - def timeout(self, value): - self._timeout = value - api_args = {'monitor': {'timeout': self._timeout}} - self._update(api_args) - - @property - def port(self): - """For HTTP(S)/SMTP/TCP probes, an alternate connection port""" - return self._port - @port.setter - def port(self, value): - self._port = value - api_args = {'monitor': {'port': self._port}} - self._update(api_args) - - @property - def path(self): - """For HTTP(S) probes, a specific path to request""" - return self._path - @path.setter - def path(self, value): - self._path = value - api_args = {'monitor': {'path': self._path}} - self._update(api_args) - - @property - def host(self): - """For HTTP(S) probes, a value to pass in to the Host""" - return self._host - @host.setter - def host(self, value): - self._host = value - api_args = {'monitor': {'host': self._host}} - self._update(api_args) - - @property - def header(self): - """For HTTP(S) probes, additional header fields/values to pass in, - separated by a newline character - """ - return self._header - @header.setter - def header(self, value): - self._header = value - api_args = {'monitor': {'header': self._header}} - self._update(api_args) +class RTTMMonitor(BaseMonitor): + """A :class:`RTTMMonitor` for RTTM Service.""" @property - def expected(self): - """For HTTP(S) probes, a string to search for in the response. For - SMTP probes, a string to compare the banner against. Failure to find - this string means the monitor will report a down status - """ - return self._expected - @expected.setter - def expected(self, value): - self._expected = value - api_args = {'monitor': {'expected': self._expected}} - self._update(api_args) - - def __str__(self): - """str override""" - return force_unicode(': {}').format(self._protocol) - __repr__ = __unicode__ = __str__ - - def __bytes__(self): - """bytes override""" - return bytes(self.__str__()) + def uri(self): + if self.zone is not None and self.fqdn is not None: + return '/RTTM/{0}/{1}/'.format(self.zone, self.fqdn) + raise ValueError -class PerformanceMonitor(Monitor): - """A :class:`PerformanceMonitor` for RTTM Service.""" +class RTTMPerformanceMonitor(RTTMMonitor): + """A :class:`RTTMPerformanceMonitor` for RTTM Service.""" def __init__(self, *args, **kwargs): - super(PerformanceMonitor, self).__init__(*args, **kwargs) + super(RTTMPerformanceMonitor, self).__init__(*args, **kwargs) self.valid_intervals = (10, 20, 30, 60) - def _get(self): - """Update this :class:`PerformanceMonitor` with data from the Dyn System - """ - uri = '/RTTM/{}/{}/'.format(self.zone, self.fqdn) - api_args = {} - response = DynectSession.get_session().execute(uri, 'GET', api_args) - self._build(response['data']['performance_monitor']) - - def _update(self, api_args): - """Update the Dyn System with data from this :class:`PerformanceMonitor` - """ - uri = '/RTTM/{}/{}/'.format(self.zone, self.fqdn) - response = DynectSession.get_session().execute(uri, 'PUT', api_args) - self._build(response['data']['performance_monitor']) - - def __str__(self): - """str override""" - return force_unicode(': {}').format(self._protocol) - __repr__ = __unicode__ = __str__ - - def __bytes__(self): - """bytes override""" - return bytes(self.__str__()) + def _build(self, data): + super(BaseMonitor, self)._build(data.pop('performance_monitor')) -class RegionPoolEntry(object): +class RegionPoolEntry(DNSAPIObject): """Creates a new RTTM service region pool entry in the zone/node indicated """ + zone = ImmutableAttribute('zone') + fqdn = ImmutableAttribute('fqdn') + region_code = ImmutableAttribute('region_code') + address = StringAttribute('address') + label = StringAttribute('label') + weight = ValidatedAttribute('weight', validator=range(1, 16)) + serve_mode = ValidatedAttribute('serve_mode', + validator=('always', 'obey', 'remove', + 'no')) + logs = ImmutableAttribute('log') + def __init__(self, address, label, weight, serve_mode, **kwargs): """Create a :class:`RegionPoolEntry` object @@ -263,127 +60,54 @@ def __init__(self, address, label, weight, serve_mode, **kwargs): :param serve_mode: Sets the behavior of this particular record. Must be one of 'always', 'obey', 'remove', or 'no' """ - super(RegionPoolEntry, self).__init__() - self.valid_modes = ('always', 'obey', 'remove', 'no') self._address = address - self._label = label - if weight not in range(1, 16): - raise DynectInvalidArgumentError('weight', weight, '1-15') - self._weight = weight - self._zone = self._fqdn = self._region_code = None - if serve_mode not in self.valid_modes: - raise DynectInvalidArgumentError('serve_mode', serve_mode, - self.valid_modes) - self._serve_mode = serve_mode - self._log = [] - for key, val in kwargs.items(): - setattr(self, key, val) + super(RegionPoolEntry, self).__init__(address, label, weight, + serve_mode, **kwargs) - def _update(self, args): - """Private method for processing various updates""" - uri = '/RTTMRegionPoolEntry/{}/{}/{}/{}/'.format(self._zone, self._fqdn, - self._region_code, - self._address) - response = DynectSession.get_session().execute(uri, 'PUT', args) - for key, val in response['data'].items(): - setattr(self, '_' + key, val) + @property + def uri(self): + uri = '/RTTMRegionPoolEntry/{zone}/{fqdn}/{region}/{address}/' + return uri.format(zone=self.zone, fqdn=self.fqdn, + region=self.region_code, address=self.address) def _get(self): - uri = '/RTTMRegionPoolEntry/{}/{}/{}/{}/'.format(self._zone, self._fqdn, - self._region_code, - self._address) args = {'detail': 'Y'} - response = DynectSession.get_session().execute(uri, 'GET', args) - for key, val in response['data'].items(): - setattr(self, '_' + key, val) - - @property - def logs(self): - self._get() - return self._log - @logs.setter - def logs(self, value): - pass - - @property - def address(self): - """The IPv4 address or FQDN of this Node IP""" - return self._address - @address.setter - def address(self, new_address): - api_args = {'new_address': new_address} - self._update(api_args) - - @property - def label(self): - """A descriptive string identifying this IP""" - return self._label - @label.setter - def label(self, value): - self._label = value - api_args = {'label': self._label} - self._update(api_args) - - @property - def weight(self): - """A number from 1-15 describing how often this record should be served. - The higher the number, the more often the address is served - """ - return self._weight - @weight.setter - def weight(self, new_weight): - if new_weight < 1 or new_weight > 15: - raise DynectInvalidArgumentError('weight', new_weight, '1-15') - self._weight = new_weight - api_args = {'weight': self._weight} - self._update(api_args) - - @property - def serve_mode(self): - """Sets the behavior of this particular record""" - return self._serve_mode - @serve_mode.setter - def serve_mode(self, serve_mode): - if serve_mode not in self.valid_modes: - raise DynectInvalidArgumentError('serve_mode', serve_mode, - self.valid_modes) - self._serve_mode = serve_mode - api_args = {'serve_mode': self._serve_mode} - self._update(api_args) - - @property - def region_code(self): - """Name of the region""" - return self._region_code - @region_code.setter - def region_code(self, value): - pass + response = DynectSession.get(self.uri, args) + self._build(response['data']) def to_json(self): """Return a JSON representation of this RegionPoolEntry""" - json_blob = {'address': self._address, 'label': self._label, - 'weight': self._weight, 'serve_mode': self._serve_mode} + json_blob = {'address': self.address, 'label': self.label, + 'weight': self.weight, 'serve_mode': self.serve_mode} return json_blob - def delete(self): - """Delete this :class:`RegionPoolEntry`""" - uri = '/RTTMRegionPoolEntry/{}/{}/{}/{}/'.format(self._zone, self._fqdn, - self._region_code, - self._address) - DynectSession.get_session().execute(uri, 'DELETE', {}) - def __str__(self): - """str override""" - return force_unicode(': {}').format(self._address) - __repr__ = __unicode__ = __str__ - - def __bytes__(self): - """bytes override""" - return bytes(self.__str__()) + return force_unicode(': {0}').format(self.address) -class RTTMRegion(object): +class RTTMRegion(DNSAPIObject): """docstring for RTTMRegion""" + uri = '/RTTMRegion/{zone}/{fqdn}/{region}/' + zone = ImmutableAttribute('zone') + fqdn = ImmutableAttribute('fqdn') + region_code = ValidatedAttribute('region_code', + validator=('US West', 'US Central', + 'US East', 'Asia', 'EU West', + 'EU Central', 'EU East', + 'global')) + autopopulate = StringAttribute('autopopulate') + ep = IntegerAttribute('ep') + apmc = IntegerAttribute('apmc') + epmc = IntegerAttribute('epmc') + serve_count = IntegerAttribute('serve_count') + failover_mode = ValidatedAttribute('failover_mode', + validator=('ip', 'cname', 'region', + 'global')) + failover_data = ValidatedAttribute('failover_data', + validator=('ip', 'cname', 'region', + 'global')) + pool = ListAttribute('pool') + def __init__(self, zone, fqdn, region_code, pool, autopopulate=None, ep=None, apmc=None, epmc=None, serve_count=None, failover_mode=None, failover_data=None): @@ -408,233 +132,143 @@ def __init__(self, zone, fqdn, region_code, pool, autopopulate=None, :param failover_data: Dependent upon failover_mode. Must be one of ip', 'cname', 'region', or 'global' """ - super(RTTMRegion, self).__init__() - self.valid_region_codes = ('US West', 'US Central', 'US East', 'Asia', - 'EU West', 'EU Central', 'EU East', 'global') - self.valid_modes = ('ip', 'cname', 'region', 'global') - self._zone = zone - self._fqdn = fqdn - if region_code not in self.valid_region_codes: - raise DynectInvalidArgumentError('region_code', region_code, - self.valid_region_codes) - self._region_code = region_code + self.uri = self.uri.format(zone=zone, fqdn=fqdn, region=region_code) if len(pool) > 0 and isinstance(pool[0], dict): - pool_list = pool - pool = [] - for item in pool_list: - rpe = RegionPoolEntry(**item) - rpe._zone = self._zone - rpe._fqdn = self._fqdn - rpe._region_code = self._region_code - pool.append(rpe) - self._pool = pool - self._autopopulate = autopopulate - self._ep = ep - self._apmc = apmc - self._epmc = epmc - self._serve_count = serve_count - self._failover_mode = failover_mode - self._failover_data = failover_data - self._status = None - self.uri = '/RTTMRegion/{}/{}/{}/'.format(self._zone, self._fqdn, - self._region_code) + self.pool = [] + for item in pool: + rpe = RegionPoolEntry(api=False, **item) + rpe.zone = self.zone + rpe.fqdn = self.fqdn + rpe.region_code = self.region_code + self.pool.append(rpe) + super(RTTMRegion, self).__init__(zone, fqdn, region_code, pool, + autopopulate, ep, apmc, epmc, + serve_count, failover_mode, + failover_data) def _post(self): """Create a new :class:`RTTMRegion` on the DynECT System""" - uri = '/RTTMRegion/{}/{}/'.format(self._zone, self._fqdn) - api_args = {'region_code': self._region_code, - 'pool': self._pool.to_json()} - if self._autopopulate: - if self._autopopulate not in ('Y', 'N'): - raise DynectInvalidArgumentError('autopopulate', - self._autopopulate, ('Y', 'N')) - api_args['autopopulate'] = self._autopopulate - if self._ep: - api_args['ep'] = self._ep - if self._apmc: - api_args['apmc'] = self._apmc - if self._epmc: - api_args['epmc'] = self._epmc - if self._serve_count: - api_args['serve_count'] = self._serve_count - if self._failover_mode: - if self._failover_mode not in self.valid_modes: - raise DynectInvalidArgumentError('failover_mode', - self._failover_mode, - self.valid_modes) - api_args['failover_mode'] = self._failover_mode - if self._failover_data: - if self._failover_data not in self.valid_modes: - raise DynectInvalidArgumentError('failover_data', - self._failover_data, - self.valid_modes) - api_args['failover_data'] = self._failover_data - response = DynectSession.get_session().execute(uri, 'POST', api_args) - self._build(response['data']) - - def _get(self): - """Get an existing :class:`RTTMRegion` object from the DynECT System""" - api_args = {} - response = DynectSession.get_session().execute(self.uri, 'GET', - api_args) + uri = '/RTTMRegion/{0}/{1}/'.format(self.zone, self.fqdn) + api_args = {'region_code': self.region_code, + 'pool': self.pool.to_json()} + if self.autopopulate: + api_args['autopopulate'] = self.autopopulate + if self.ep: + api_args['ep'] = self.ep + if self.apmc: + api_args['apmc'] = self.apmc + if self.epmc: + api_args['epmc'] = self.epmc + if self.serve_count: + api_args['serve_count'] = self.serve_count + if self.failover_mode: + api_args['failover_mode'] = self.failover_mode + if self.failover_data: + api_args['failover_data'] = self.failover_data + response = DynectSession.post(uri, api_args) self._build(response['data']) def _update(self, api_args): """Private Update method to cut back on redundant code""" - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) + response = DynectSession.put(self.uri, api_args) self._build(response['data']) def _build(self, data): - for key, val in data.items(): - if key == 'pool': - pass - else: - setattr(self, '_' + key, val) - - @property - def autopopulate(self): - """If set to Y, this region will automatically be filled in from the - global pool, and any other options passed in for this region will be - ignored. Must be either 'Y' or 'N'. - """ - return self._autopopulate - @autopopulate.setter - def autopopulate(self, value): - self._autopopulate = value - api_args = {'autopopulate': self._autopopulate} - self._update(api_args) - - @property - def ep(self): - """Eligibility Pool - How many records will make it into the eligibility - pool. The addresses that get chosen will be those that respond the - fastest - """ - return self._ep - @ep.setter - def ep(self, value): - self._ep = value - api_args = {'ep': self._ep} - self._update(api_args) - - @property - def apmc(self): - """The minimum amount of IPs that must be in the up state, otherwise the - region will be in failover. - """ - return self._apmc - @apmc.setter - def apmc(self, value): - self._apmc = value - api_args = {'apmc': self._apmc} - self._update(api_args) - - @property - def epmc(self): - """The minimum amount of IPs that must be populated in the EP, otherwise - the region will be in failover - """ - return self._epmc - @epmc.setter - def epmc(self, value): - self._epmc = value - api_args = {'epmc': self._epmc} - self._update(api_args) + data.pop('pool', None) + super(RTTMRegion, self)._build(data) - @property - def serve_count(self): - """How many records will be returned in each DNS response""" - return self._serve_count - @serve_count.setter - def serve_count(self, value): - self._serve_count = value - api_args = {'serve_count': self._serve_count} - self._update(api_args) - - @property - def failover_mode(self): - """How the region should failover. Must be one of 'ip', 'cname', - 'region', or 'global' - """ - return self._failover_mode - @failover_mode.setter - def failover_mode(self, value): - self._failover_mode = value - api_args = {'failover_mode': self._failover_mode} - self._update(api_args) - - @property - def failover_data(self): - """Dependent upon failover_mode. Must be one of ip', 'cname', 'region', - or 'global' - """ - return self._failover_data - @failover_data.setter - def failover_data(self, value): - if value not in self.valid_modes: - raise DynectInvalidArgumentError('failover_data', value, - self.valid_modes) - api_args = {'failover_data': value} - self._update(api_args) - - @property - def pool(self): - """The IP Pool list for this :class:`RTTMRegion`""" - return self._pool - @pool.setter - def pool(self, value): - self._pool = value - api_args = {'pool': self._pool} - self._update(api_args) - - @property - def status(self): - """The current state of the region.""" - self._get() - return self._status - @status.setter - def status(self, value): - pass - - @property - def _json(self): + def to_json(self): """Unpack this object and return it as a JSON blob""" - json_blob = {'region_code': self._region_code, - 'pool': [entry.to_json() for entry in self._pool]} - if self._autopopulate: - json_blob['autopopulate'] = self._autopopulate - if self._ep: - json_blob['ep'] = self._ep - if self._apmc: - json_blob['apmc'] = self._apmc - if self._epmc: - json_blob['epmc'] = self._epmc - if self._serve_count: - json_blob['serve_count'] = self._serve_count - if self._failover_mode: - json_blob['failover_mode'] = self._failover_mode - if self._failover_data: - json_blob['failover_data'] = self._failover_data + json_blob = {'region_code': self.region_code, + 'pool': [entry.to_json() for entry in self.pool]} + if self.autopopulate: + json_blob['autopopulate'] = self.autopopulate + if self.ep: + json_blob['ep'] = self.ep + if self.apmc: + json_blob['apmc'] = self.apmc + if self.epmc: + json_blob['epmc'] = self.epmc + if self.serve_count: + json_blob['serve_count'] = self.serve_count + if self.failover_mode: + json_blob['failover_mode'] = self.failover_mode + if self.failover_data: + json_blob['failover_data'] = self.failover_data return json_blob - def delete(self): - """Delete an existing :class:`RTTMRegion` object from the DynECT - System - """ - DynectSession.get_session().execute(self.uri, 'DELETE') - def __str__(self): - """str override""" - return force_unicode(': {}').format(self._region_code) - __repr__ = __unicode__ = __str__ + return force_unicode(': {0}').format(self.region_code) - def __bytes__(self): - """bytes override""" - return bytes(self.__str__()) +class RTTM(APIService, DNSAPIObject): + """Real Time Traffic Management (RTTM) is a DynECT Managed DNS service, + which monitors all of your endpoints to detect the best-performing ones and + also auto-populates your regional pools using that information to provide + you with the lowest latency possible. Differing from GSLB, RTTM collects + real-time performance data on the load time of each of your endpoints, + rather than just routing your traffic to manually entered, static + configurations. + """ + uri = '/RTTM/{zone}/{fqdn}/' + + #: The zone that this :class:`RTTM` service is attached to + zone = ImmutableAttribute('zone') + + #: The FQDN of the node that this :class:`RTTM` service is attached to + fqdn = ImmutableAttribute('fqdn') + + #: Indicates whether or not the service should automatically come out of + #: failover when the IP addresses resume active status or if the service + #: should remain in failover until manually reset. Must be one of + #: :const:`True`, :const:`False`, 'Y', or 'N' + auto_recover = ValidatedAttribute('auto_recover', validator=('Y', 'N')) + + #: Time To Live in seconds of records in the service. Must be less than + #: 1/2 of the :class:`RTTMMonitor`'s monitoring interval. Must be one of + #: 30, 60, 150, 300, or 450. + ttl = ValidatedAttribute('ttl', validator=(30, 60, 150, 300, 450)) + + #: A list of the events which trigger notifications. Must be one of 'ip', + #: 'svc', or 'nosrv' + notify_events = ValidatedListAttribute('notify_events', + validator=('ip', 'svc', 'nosrv')) + + #: The Hostname or IP address of a server to receive syslog notifications + #: on monitoring events + syslog_server = StringAttribute('syslog_server') + + #: The port where the remote syslog server listens for notifications + syslog_port = IntegerAttribute('syslog_port') + + #: The ident to use when sending syslog notifications + syslog_ident = StringAttribute('syslog_ident') + + #: The syslog facility to use when sending syslog notifications. Must be + #: one of kern, user, mail, daemon, auth, syslog, lpr, news, uucp, cron, + #: authpriv, ftp, ntp, security, console, local0, local1, local2, local3, + #: local4, local5, local6, or local7 + syslog_facility = ValidatedAttribute('syslog_facility', + validator=( + 'kern', 'user', 'mail', 'daemon', + 'auth', 'syslog', 'lpr', 'news', + 'uucp', 'cron', 'authpriv', 'ftp', + 'ntp', 'security', 'console', + 'local0', 'local1', 'local2', + 'local3', 'local4', 'local5', + 'local6', 'local7' + )) + + #: The :class:`RTTMMonitor` for this service + monitor = ClassAttribute('monitor', class_type=RTTMMonitor) + + #: The :class:`RTTMPerformanceMonitor` for this service + performance_monitor = ClassAttribute('performance_monitor', + class_type=RTTMPerformanceMonitor) + + #: Name of contact to receive notifications + contact_nickname = StringAttribute('contact_nickname') -class RTTM(object): def __init__(self, zone, fqdn, *args, **kwargs): """Create a :class:`RTTM` object @@ -643,8 +277,8 @@ def __init__(self, zone, fqdn, *args, **kwargs): active status or if the service should remain in failover until manually reset. Must be one of 'Y' or 'N' :param ttl: Time To Live in seconds of records in the service. Must be - less than 1/2 of the Health Probe's monitoring interval. Must be one - of 30, 60, 150, 300, or 450. + less than 1/2 of the Health Probe's monitoring interval. Must be + one of 30, 60, 150, 300, or 450. :param notify_events: A list of the events which trigger notifications. Must be one of 'ip', 'svc', or 'nosrv' :param syslog_server: The Hostname or IP address of a server to receive @@ -658,147 +292,86 @@ def __init__(self, zone, fqdn, *args, **kwargs): console, local0, local1, local2, local3, local4, local5, local6, or local7 :param region: A list of :class:`RTTMRegion`'s - :param monitor: The :class:`Monitor` for this service + :param monitor: The :class:`RTTMMonitor` for this service :param performance_monitor: The performance monitor for the service :param contact_nickname: Name of contact to receive notifications """ - super(RTTM, self).__init__() - self.valid_ttls = (30, 60, 150, 300, 450) - self.valid_notify_events = ('ip', 'svc', 'nosrv') - self.valid_syslog_facilities = ('kern', 'user', 'mail', 'daemon', - 'auth', 'syslog', 'lpr', 'news', 'uucp', - 'cron', 'authpriv', 'ftp', 'ntp', - 'security', 'console', 'local0', - 'local1', 'local2', 'local3', 'local4', - 'local5', 'local6', 'local7') - self._zone = zone - self._fqdn = fqdn - self.uri = '/RTTM/{}/{}/'.format(self._zone, self._fqdn) - self._auto_recover = self._ttl = self._notify_events = None - self._syslog_server = self._syslog_port = self._syslog_ident = None - self._syslog_facility = self._monitor = self._performance_monitor = None - self._contact_nickname = self._active = None - self._region = APIList(DynectSession.get_session, 'region') - if 'api' in kwargs: - del kwargs['api'] - self._build(kwargs) - elif len(args) == 0 and len(kwargs) == 0: - self._get() - else: - self._post(*args, **kwargs) - self._region.uri = self.uri + self.uri = self.uri.format(zone=zone, fqdn=fqdn) + self._zone, self._fqdn = zone, fqdn + super(RTTM, self).__init__(*args, **kwargs) + self.region.uri = self.uri def _post(self, contact_nickname, performance_monitor, region, ttl=None, auto_recover=None, notify_events=None, syslog_server=None, syslog_port=514, syslog_ident='dynect', syslog_facility='daemon', monitor=None): """Create a new RTTM Service on the DynECT System""" - self._auto_recover = auto_recover - self._ttl = ttl - self._notify_events = notify_events - self._syslog_server = syslog_server - self._syslog_port = syslog_port - self._syslog_ident = syslog_ident - self._syslog_facility = syslog_facility - self._region += region - self._monitor = monitor - self._performance_monitor = performance_monitor - self._contact_nickname = contact_nickname - api_args = {} - if auto_recover: - if auto_recover not in ('Y', 'N'): - raise DynectInvalidArgumentError('auto_recover', auto_recover, - ('Y', 'N')) - api_args['auto_recover'] = self._auto_recover - if ttl: - if ttl not in self.valid_ttls: - raise DynectInvalidArgumentError('ttl', ttl, self.valid_ttls) - api_args['ttl'] = self._ttl - if notify_events: - for event in notify_events: - if event not in self.valid_notify_events: - raise DynectInvalidArgumentError('notify_events', event, - self.valid_notify_events) - api_args['notify_events'] = self._notify_events - if syslog_server: - api_args['syslog_server'] = self._syslog_server - if syslog_port: - api_args['syslog_port'] = self._syslog_port - if syslog_ident: - api_args['syslog_ident'] = self._syslog_ident - if syslog_facility: - if syslog_facility not in self.valid_syslog_facilities: - raise DynectInvalidArgumentError('syslog_facility', - syslog_facility, - self.valid_syslog_facilities) - api_args['syslog_facility'] = self._syslog_facility - if region: - api_args['region'] = [region._json for region in self._region] + api_args = {'contact_nickname': contact_nickname, + 'performance_monitor': performance_monitor.to_json(), + 'region': [reg.to_json() for reg in region], 'ttl': ttl, + 'auto_recover': auto_recover, + 'notify_events': notify_events, + 'syslog_server': syslog_server, 'syslog_port': syslog_port, + 'syslog_ident': syslog_ident, + 'syslog_facility': syslog_facility} if monitor: - api_args['monitor'] = self._monitor.to_json() + api_args['monitor'] = monitor.to_json() if performance_monitor: - mon_args = self._performance_monitor.to_json() - api_args['performance_monitor'] = mon_args - if contact_nickname: - api_args['contact_nickname'] = self._contact_nickname + api_args['performance_monitor'] = performance_monitor.to_json() # API expects a CSV string, not a list if isinstance(self.notify_events, list): - api_args['notify_events'] = ', '.join(self.notify_events) - - response = DynectSession.get_session().execute(self.uri, 'POST', - api_args) - self._build(response['data']) + api_args['notify_events'] = ', '.join(notify_events) - def _get(self): - """Build an object around an existing DynECT RTTM Service""" - api_args = {} - response = DynectSession.get_session().execute(self.uri, 'GET', - api_args) - self._build(response['data']) - - def _update(self, api_args): - """Perform a PUT api call using this objects data""" - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) + api_args = {api_args[k] for k in api_args if api_args[k] is not None} + resp = DynectSession.post(self.uri, api_args) + self._build(resp['data']) def _build(self, data): """Build the neccesary substructures under this :class:`RTTM`""" - for key, val in data.items(): - if key == 'region': - self._region = APIList(DynectSession.get_session, 'region') - for region in val: - code = region.pop('region_code', None) - pool = region.pop('pool', None) - status = region.pop('status', None) - r = RTTMRegion(self._zone, self._fqdn, code, pool, **region) - r._status = status - self._region.append(r) - elif key == 'monitor': - if self._monitor is not None: - self._monitor.zone = self._zone - self._monitor.fqdn = self._fqdn - else: - proto = val.pop('protocol', None) - inter = val.pop('interval', None) - self._monitor = Monitor(proto, inter, **val) - elif key == 'performance_monitor': - if self._performance_monitor is not None: - self._performance_monitor.zone = self._zone - self._performance_monitor.fqdn = self._fqdn - else: - proto = val.pop('protocol', None) - inter = val.pop('interval', None) - self._performance_monitor = PerformanceMonitor(proto, inter, - **val) - elif key == 'notify_events': - self._notify_events = [item.strip() for item in val.split(',')] - elif key == 'active': - self._active = Active(val) + if 'region' in data: + self.region = APIList(DynectSession.get_session, 'region') + for region in data.pop('region'): + code = region.pop('region_code', None) + pool = region.pop('pool', None) + status = region.pop('status', None) + r = RTTMRegion(self.zone, self.fqdn, code, pool, **region) + r._status = status + self.region.append(r) + self.region.uri = self.uri + if 'monitor' in data: + if self.monitor is not None: + self.monitor.zone = self.zone + self.monitor.fqdn = self.fqdn + else: + monitor = data.pop('monitor') + proto = monitor.pop('protocol', None) + inter = monitor.pop('interval', None) + self.monitor = RTTMMonitor(proto, inter, **monitor) + if 'performance_monitor' in data: + if self.performance_monitor is not None: + self.performance_monitor.zone = self.zone + self.performance_monitor.fqdn = self.fqdn else: - setattr(self, '_' + key, val) - self._region.uri = self.uri + monitor = data.pop('performance_monitor') + proto = monitor.pop('protocol', None) + inter = monitor.pop('interval', None) + self.performance_monitor = RTTMPerformanceMonitor(proto, inter, + **monitor) + if 'notify_events' in data: + self._notify_events = [item.strip() for item in + data.pop('notify_events').split(',')] + if 'active' in data: + self._active = Active(data.pop('active')) + super(RTTM, self)._build(data) + + def _update(self, **api_args): + if 'monitor' in api_args: + api_args['monitor'] = api_args['monitor'].to_json() + if 'performance_monitor' in api_args: + pm = api_args['performance_monitor'] + api_args['performance_monitor'] = pm.to_json() + super(RTTM, self)._update(**api_args) def get_rrset_report(self, ts): """Generates a report of regional response sets for this RTTM service @@ -807,11 +380,8 @@ def get_rrset_report(self, ts): :param ts: UNIX timestamp identifying point in time for the log report :return: dictionary containing rrset report data """ - api_args = {'zone': self._zone, - 'fqdn': self._fqdn, - 'ts': ts} - response = DynectSession.get_session().execute('/RTTMRRSetReport/', - 'POST', api_args) + api_args = {'zone': self.zone, 'fqdn': self.fqdn, 'ts': ts} + response = DynectSession.post('/RTTMRRSetReport/', api_args) return response['data'] def get_log_report(self, start_ts, end_ts=None): @@ -825,205 +395,21 @@ def get_log_report(self, start_ts, end_ts=None): :return: dictionary containing log report data """ end_ts = end_ts or datetime.now() - api_args = {'zone': self._zone, - 'fqdn': self._fqdn, + api_args = {'zone': self.zone, 'fqdn': self.fqdn, 'start_ts': unix_date(start_ts), 'end_ts': unix_date(end_ts)} - response = DynectSession.get_session().execute('/RTTMLogReport/', - 'POST', api_args) + response = DynectSession.post('/RTTMLogReport/', api_args) return response['data'] - def activate(self): - """Activate this RTTM Service""" - api_args = {'activate': True} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) - - def deactivate(self): - """Deactivate this RTTM Service""" - api_args = {'deactivate': True} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) - def recover(self, recoverip=None, address=None): - """Recovers the RTTM service or a specific node IP within the service""" + """Recovers the RTTM service or a specific node IP within the service + """ api_args = {'recover': True} if recoverip: api_args['recoverip'] = recoverip api_args['address'] = address - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) - - @property - def active(self): - """Returns whether or not this :class:`RTTM` Service is currently - active. When setting directly, rather than using activate/deactivate - valid arguments are 'Y' or True to activate, or 'N' or False to - deactivate. Note: If your service is already active and you try to - activate it, nothing will happen. And vice versa for deactivation. - - :returns: An :class:`Active` object representing the current state of - this :class:`ReverseDNS` Service - """ - return self._active - @active.setter - def active(self, value): - deactivate = ('N', False) - activate = ('Y', True) - if value in deactivate and self.active: - self.deactivate() - elif value in activate and not self.active: - self.activate() - - @property - def auto_recover(self): - """Indicates whether or not the service should automatically come out of - failover when the IP addresses resume active status or if the service - should remain in failover until manually reset. Must be one of 'Y' or - 'N' - """ - return self._auto_recover - @auto_recover.setter - def auto_recover(self, value): - if value not in ('Y', 'N'): - raise DynectInvalidArgumentError('auto_recover', value, - ('Y', 'N')) - api_args = {'auto_recover': value} - self._update(api_args) - - @property - def ttl(self): - """Time To Live in seconds of records in the service. Must be less than - 1/2 of the Health Probe's monitoring interval. Must be one of 30, 60, - 150, 300, or 450. - """ - return self._ttl - @ttl.setter - def ttl(self, value): - if value not in self.valid_ttls: - raise DynectInvalidArgumentError('ttl', value, self.valid_ttls) - api_args = {'ttl': value} - self._update(api_args) - - @property - def notify_events(self): - """A list of events which trigger notifications. Valid values are: 'ip', - 'svc', and 'nosrv' - """ - return self._notify_events - @notify_events.setter - def notify_events(self, value): - for val in value: - if val not in self.valid_notify_events: - raise DynectInvalidArgumentError('notify_events', val, - self.valid_notify_events) - value = ', '.join(value) - api_args = {'notify_events': value} - self._update(api_args) - - @property - def syslog_server(self): - """The Hostname or IP address of a server to receive syslog - notifications on monitoring events - """ - return self._syslog_server - @syslog_server.setter - def syslog_server(self, value): - api_args = {'syslog_server': value} - self._update(api_args) - - @property - def syslog_port(self): - """The port where the remote syslog server listens for notifications""" - return self._syslog_port - @syslog_port.setter - def syslog_port(self, value): - api_args = {'syslog_port': value} - self._update(api_args) - - @property - def syslog_ident(self): - """The ident to use when sending syslog notifications""" - return self._syslog_ident - @syslog_ident.setter - def syslog_ident(self, value): - api_args = {'syslog_ident': value} - self._update(api_args) - - @property - def syslog_facility(self): - """The syslog facility to use when sending syslog notifications. Must - be one of kern, user, mail, daemon, auth, syslog, lpr, news, uucp, cron, - authpriv, ftp, ntp, security, console, local0, local1, local2, local3, - local4, local5, local6, or local7 - """ - return self._syslog_facility - @syslog_facility.setter - def syslog_facility(self, value): - if value not in self.valid_syslog_facilities: - raise DynectInvalidArgumentError('syslog_facility', value, - self.valid_syslog_facilities) - api_args = {'syslog_facility': value} - self._update(api_args) - - @property - def region(self): - """A list of :class:`RTTMRegion`'s""" - return self._region - @region.setter - def region(self, value): - if isinstance(value, list) and not isinstance(value, APIList): - self._region = APIList(DynectSession.get_session, 'region', None, - value) - elif isinstance(value, APIList): - self._region = value - self._region.uri = self.uri - - @property - def monitor(self): - """The :class:`Monitor` for this service""" - return self._monitor - @monitor.setter - def monitor(self, value): - if isinstance(value, Monitor): - self._monitor = value - api_args = {'monitor': self._monitor.to_json()} - self._update(api_args) - - @property - def performance_monitor(self): - """The Performance :class:`Monitor` for this service""" - return self._performance_monitor - @performance_monitor.setter - def performance_monitor(self, value): - if isinstance(value, Monitor): - self._performance_monitor = value - api_args = {'performance_monitor': - self._performance_monitor.to_json()} - self._update(api_args) - - @property - def contact_nickname(self): - """The name of contact to receive notifications from this service""" - return self._contact_nickname - @contact_nickname.setter - def contact_nickname(self, value): - api_args = {'contact_nickname': value} - self._update(api_args) - - def delete(self): - """Delete this RTTM Service""" - api_args = {} - DynectSession.get_session().execute(self.uri, 'DELETE', api_args) + resp = DynectSession.put(self.uri, api_args) + self._build(resp['data']) def __str__(self): - """str override""" - return force_unicode(': {}').format(self._fqdn) - __repr__ = __unicode__ = __str__ - - def __bytes__(self): - """bytes override""" - return bytes(self.__str__()) + return force_unicode(': {0}').format(self.fqdn) diff --git a/dyn/tm/session.py b/dyn/tm/session.py index bc3126f..606874a 100644 --- a/dyn/tm/session.py +++ b/dyn/tm/session.py @@ -5,11 +5,13 @@ own respective functionality. """ # API Libs -from ..core import SessionEngine from .errors import * +from ..core import SessionEngine, APIObject from ..compat import force_unicode from ..encrypt import AESCipher +__all__ = ['DynectSession', 'DNSAPIObject'] + class DynectSession(SessionEngine): """Base object representing a DynectSession Session""" @@ -120,16 +122,10 @@ def user_permissions_report(self, user_name=None): :param user_name: The user whose permissions will be returned. Defaults to the current user """ - api_args = dict() - api_args['user_name'] = user_name or self.username + api_args = {'user_name': user_name or self.username} uri = '/UserPermissionReport/' response = self.execute(uri, 'POST', api_args) - permissions = [] - for key, val in response['data'].items(): - if key == 'allowed': - for permission in val: - permissions.append(permission['name']) - return permissions + return [perm['name'] for perm in response['data'].get('allowed', [])] @property def permissions(self): @@ -137,9 +133,6 @@ def permissions(self): if self._permissions is None: self._permissions = self.user_permissions_report() return self._permissions - @permissions.setter - def permissions(self, value): - pass def authenticate(self): """Authenticate to the DynectSession service with the provided @@ -159,7 +152,7 @@ def authenticate(self): def log_out(self): """Log the current session out from the DynECT API system""" - self.execute('/Session/', 'DELETE', {}) + self.execute('/Session/', 'DELETE') self.close_session() @property @@ -173,3 +166,10 @@ def __str__(self): header = super(DynectSession, self).__str__() return header + force_unicode(': {}, {}').format(self.customer, self.username) + + +class DNSAPIObject(APIObject): + """:class:`APIObject` subclass with a default `session_type` of + :class:`~dyn.tm.session.DynectSession` + """ + session_type = DynectSession diff --git a/dyn/tm/tools.py b/dyn/tm/tools.py index 5099f14..a9f4265 100644 --- a/dyn/tm/tools.py +++ b/dyn/tm/tools.py @@ -17,14 +17,14 @@ def change_ip(zone, from_ip, to, v6=False, publish=False): you want updated :param to: Either a list of ip addresses or a single ip address that will overwrite from_ip - :param v6: Boolean flag to specify if we're replacing ipv4 or ipv6 addresses - (ie, whether we're updating an ARecord or AAAARecord) + :param v6: Boolean flag to specify if we're replacing ipv4 or ipv6 + addresses (ie, whether we're updating an ARecord or AAAARecord) :param publish: A boolean flag denoting whether or not to publish changes after making them. You can optionally leave this as *False* and process the returned changeset prior to publishing your changes. :returns: A list of tuples of the form (fqdn, old, new) where fqdn is - the fqdn of the record that was updated, old was the old ip address, and - new is the new ip address. + the fqdn of the record that was updated, old was the old ip address, + and new is the new ip address. """ records = zone.get_all_records() records = records['aaaa_records'] if v6 else records['a_records'] @@ -60,14 +60,14 @@ def map_ips(zone, mapping, v6=False, publish=False): :param zone: The :class:`~dyn.tm.zones.Zone` you wish to update ips for :param mapping: A *dict* of the form {'old_ip': 'new_ip'} - :param v6: Boolean flag to specify if we're replacing ipv4 or ipv6 addresses - (ie, whether we're updating an ARecord or AAAARecord) + :param v6: Boolean flag to specify if we're replacing ipv4 or ipv6 + addresses (ie, whether we're updating an ARecord or AAAARecord) :param publish: A boolean flag denoting whether or not to publish changes after making them. You can optionally leave this as *False* and process the returned changeset prior to publishing your changes. :returns: A list of tuples of the form (fqdn, old, new) where fqdn is - the fqdn of the record that was updated, old was the old ip address, and - new is the new ip address. + the fqdn of the record that was updated, old was the old ip address, + and new is the new ip address. """ records = zone.get_all_records() records = records['aaaa_records'] if v6 else records['a_records'] diff --git a/dyn/tm/utils.py b/dyn/tm/utils.py index ed470f3..6561e05 100644 --- a/dyn/tm/utils.py +++ b/dyn/tm/utils.py @@ -59,7 +59,8 @@ def insert(self, index, item): return response def pop(self, *args, **kwargs): - """Handle the removal via pop of an item in this list via an API Call""" + """Handle the removal via pop of an item in this list via an API Call + """ response = super(APIList, self).pop(*args, **kwargs) self._update(self.__build_args()) return response @@ -78,13 +79,14 @@ def __iadd__(self, other): def __build_args(self): """Convert this list into an API Args dict""" - my_list = [x._json for x in self if x is not None] + my_list = [x.to_json() for x in self if x is not None] return {self.name: my_list} def _update(self, api_args): """Private update (PUT) method""" if self.session_func is not None and self.uri is not None: - response = self.session_func().execute(self.uri, 'PUT', api_args) + response = self.session_func.get_session().execute(self.uri, 'PUT', + api_args) data = response['data'][self.name] for new_data, item in zip(data, self): item._update(new_data) @@ -100,8 +102,8 @@ class Active(object): """Object for intercepting the active attribute of most services which return a non-pythonic 'Y' or 'N' as the active status. This class aims to allow for more pythonic interactions with these attributes by allowing the - active field to be represented as either it's boolean representation or it's - string 'Y' or 'N' representation. + active field to be represented as either it's boolean representation or + it's string 'Y' or 'N' representation. """ def __init__(self, inp): """Accept either a string 'Y' or 'N' or a bool as input @@ -122,8 +124,8 @@ def __nonzero__(self): """ return self.value - # For forwards compatibility with Python 3.x where __bool__ is called when - # evaluating boolean expressions + # For Python 2.x/3.x compatibility where __bool__ or __nonzero__ can be + # called when evaluating boolean expressions __bool__ = __nonzero__ def __str__(self): diff --git a/dyn/tm/zones.py b/dyn/tm/zones.py index ae7569f..e6f5e71 100644 --- a/dyn/tm/zones.py +++ b/dyn/tm/zones.py @@ -1,29 +1,22 @@ # -*- coding: utf-8 -*- """This module contains all Zone related API objects.""" import os -import logging from time import sleep from datetime import datetime from .utils import unix_date +from .errors import DynectCreateError, DynectGetError +from .records import RECORD_TYPES +from .session import DynectSession, DNSAPIObject +from .services import (ActiveFailover, DynamicDNS, DNSSEC, TrafficDirector, + GSLB, ReverseDNS, RTTM, HTTPRedirect) +from ..core import (IntegerAttribute, StringAttribute, ListAttribute, + ImmutableAttribute, ValidatedAttribute) from ..compat import force_unicode -from .errors import * -from .records import * -from .session import DynectSession -from .services import * __author__ = 'jnappi' __all__ = ['get_all_zones', 'Zone', 'SecondaryZone', 'Node'] -RECS = {'A': ARecord, 'AAAA': AAAARecord, 'CERT': CERTRecord, - 'CNAME': CNAMERecord, 'DHCID': DHCIDRecord, 'DNAME': DNAMERecord, - 'DNSKEY': DNSKEYRecord, 'DS': DSRecord, 'KEY': KEYRecord, - 'KX': KXRecord, 'LOC': LOCRecord, 'IPSECKEY': IPSECKEYRecord, - 'MX': MXRecord, 'NAPTR': NAPTRRecord, 'PTR': PTRRecord, 'PX': PXRecord, - 'NSAP': NSAPRecord, 'RP': RPRecord, 'NS': NSRecord, 'SOA': SOARecord, - 'SPF': SPFRecord, 'SRV': SRVRecord, 'TLSA': TLSARecord, - 'TXT': TXTRecord} - def get_all_zones(): """Accessor function to retrieve a *list* of all @@ -33,7 +26,7 @@ def get_all_zones(): """ uri = '/Zone/' api_args = {'detail': 'Y'} - response = DynectSession.get_session().execute(uri, 'GET', api_args) + response = DynectSession.get(uri, api_args) zones = [] for zone in response['data']: zones.append(Zone(zone['zone'], api=False, **zone)) @@ -48,15 +41,46 @@ def get_all_secondary_zones(): """ uri = '/Secondary/' api_args = {'detail': 'Y'} - response = DynectSession.get_session().execute(uri, 'GET', api_args) + response = DynectSession.get(uri, api_args) zones = [] for zone in response['data']: zones.append(SecondaryZone(zone.pop('zone'), api=False, **zone)) return zones -class Zone(object): - """A class representing a DynECT Zone""" +# noinspection PyUnresolvedReferences +class Zone(DNSAPIObject): + """A class representing a DynECT Primary Zone""" + #: Primary Zone URI + uri = '/Zone/{zone_name}/' + + #: The name of this Zone + zone = ImmutableAttribute('zone') + + #: Alias to zone + name = ImmutableAttribute('zone') + + #: The fully qualified domain name of this Zone + fqdn = ImmutableAttribute('fqdn') + + #: The style of this zone's serial. Valid values are increment, epoch, day, + #: and minute + serial_style = ValidatedAttribute('serial_style', + validator=('increment', 'epoch', 'day', + 'minute')) + + #: The current serial of this zone, the format of the serial will be + #: dependent on the value of serial_style + serial = IntegerAttribute('serial') + + #: Convenience property for this :class:`Zone`. If a :class:`Zone` is + #: frozen, the status will read as `'frozen'`, if the :class:`Zones` is not + #: frozen the status will read as `'active'`. Because the API does not + #: return information about whether or not a :class:`Zones` is frozen there + #: will be a few cases where this status will be `None` in order to avoid + #: guessing what the current status actually is. + status = StringAttribute('status') + def __init__(self, name, *args, **kwargs): """Create a :class:`Zone` object. Note: When creating a new :class:`Zone` if no contact is specified the path to a local zone @@ -75,28 +99,11 @@ def __init__(self, name, *args, **kwargs): :param timeout: The time, in minutes, to wait for a zone xfer to complete """ - super(Zone, self).__init__() - self.valid_serials = ('increment', 'epoch', 'day', 'minute') - self._name = name - self._fqdn = self._name - if self._fqdn and not self._fqdn.endswith('.'): - self._fqdn += '.' - self._contact = self._ttl = self._serial_style = self._serial = None - self._zone = self._status = None - self.records = {} - self.services = {} - self.uri = '/Zone/{}/'.format(self._name) - if 'api' in kwargs: - del kwargs['api'] - for key, val in kwargs.items(): - setattr(self, '_' + key, val) - self._name = self._zone - self.uri = '/Zone/{}/'.format(self._name) - elif len(args) == 0 and len(kwargs) == 0: - self._get() - else: - self._post(*args, **kwargs) - self._status = 'active' + self.uri = self.uri.format(zone_name=name) + self.records, self.services = dict(), dict() + self._contact = self._ttl = None + super(Zone, self).__init__(*args, **kwargs) + self._fqdn = name if name.endswith('.') else '{0}.'.format(name) def _post(self, contact=None, ttl=60, serial_style='increment', file_name=None, master_ip=None, timeout=None): @@ -108,19 +115,14 @@ def _post(self, contact=None, ttl=60, serial_style='increment', elif master_ip is not None: self._xfer(master_ip, timeout) else: - self._contact = contact - self._ttl = ttl - if serial_style not in self.valid_serials: - raise DynectInvalidArgumentError(serial_style, - self.valid_serials) + # Assign serial style here to force pre-api validation self._serial_style = serial_style - api_args = {'zone': self._name, - 'rname': self._contact, - 'ttl': self._ttl, - 'serial_style': self._serial_style} - response = DynectSession.get_session().execute(self.uri, 'POST', - api_args) + + api_args = {'zone': self.name, 'rname': contact, 'ttl': ttl, + 'serial_style': serial_style} + response = DynectSession.post(self.uri, api_args) self._build(response['data']) + self._status = 'active' def _post_with_file(self, file_name): """Create a :class:`Zone` from a RFC1035 style Master file. A ZoneFile @@ -134,25 +136,25 @@ def _post_with_file(self, file_name): raise DynectInvalidArgumentError('Zone File Size', file_size, 'Under 1MB') else: - uri = '/ZoneFile/{}/'.format(self.name) + uri = '/ZoneFile/{0}/'.format(self.name) f = open(full_path, 'r') content = f.read() f.close() api_args = {'file': content} - DynectSession.get_session().execute(uri, 'POST', api_args) + DynectSession.post(uri, api_args) self.__poll_for_get() def _xfer(self, master_ip, timeout=None): """Create a :class:`Zone` by ZoneTransfer by providing an optional master_ip argument. """ - uri = '/ZoneTransfer/{}/'.format(self.name) + uri = '/ZoneTransfer/{0}/'.format(self.name) api_args = {'master_ip': master_ip} - DynectSession.get_session().execute(uri, 'POST', api_args) + DynectSession.post(uri, api_args) time_out = timeout or 10 count = 0 while count < time_out: - response = DynectSession.get_session().execute(uri, 'GET', {}) + response = DynectSession.get(uri) if response['status'] == 'running' and response['message'] == '': sleep(60) count += 1 @@ -176,54 +178,23 @@ def __poll_for_get(self, n_loops=10, xfer=False, xfer_master_ip=None): sleep(2) count += 1 if not got and xfer: - uri = '/ZoneTransfer/{}/'.format(self.name) + uri = '/ZoneTransfer/{0}/'.format(self.name) api_args = {} if xfer_master_ip is not None: api_args['master_ip'] = xfer_master_ip - response = DynectSession.get_session().execute(uri, 'GET', api_args) + response = DynectSession.get(uri, api_args) error_labels = ['running', 'waiting', 'failed', 'canceled'] ok_labels = ['ready', 'unpublished', 'ok'] if response['data']['status'] in error_labels: raise DynectCreateError(response['msgs']) elif response['data']['status'] in ok_labels: self._get() - else: - pass # Should never get here - - def _get(self): - """Get an existing :class:`Zone` object from the DynECT System""" - api_args = {} - response = DynectSession.get_session().execute(self.uri, 'GET', - api_args) - self._build(response['data']) - - def _build(self, data): - """Build the variables in this object by pulling out the data from data - """ - for key, val in data.items(): - setattr(self, '_' + key, val) @property def __root_soa(self): """Return the SOA record associated with this Zone""" return self.get_all_records_by_type('SOA')[0] - @property - def name(self): - """The name of this :class:`Zone`""" - return self._name - @name.setter - def name(self, value): - pass - - @property - def fqdn(self): - """The name of this :class:`Zone`""" - return self._fqdn - @fqdn.setter - def fqdn(self, value): - pass - @property def contact(self): """The email address of the primary :class:`Contact` associated with @@ -246,77 +217,34 @@ def ttl(self): def ttl(self, value): self.__root_soa.ttl = value - @property - def serial(self): - """The current serial of this :class:`Zone`""" - return self._serial - @serial.setter - def serial(self, value): - pass - - @property - def serial_style(self): - """The current serial style of this :class:`Zone`""" - return self._serial_style - @serial_style.setter - def serial_style(self, value): - if not value in self.valid_serials: - raise DynectInvalidArgumentError('serial_style', value, - self.valid_serials) - self.__root_soa.serial_style = value - - @property - def status(self): - """Convenience property for :class:`Zones`. If a :class:`Zones` is - frozen the status will read as `'frozen'`, if the :class:`Zones` is not - frozen the status will read as `'active'`. Because the API does not - return information about whether or not a :class:`Zones` is frozen there - will be a few cases where this status will be `None` in order to avoid - guessing what the current status actually is. - """ - return self._status - @status.setter - def status(self, value): - pass - def freeze(self): - """Causes the zone to become frozen. Freezing a zone prevents changes to - the zone until it is thawed. + """Causes the zone to become frozen. Freezing a zone prevents changes + to the zone until it is thawed. """ - api_args = {'freeze': True} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) - if response['status'] == 'success': - self._status = 'frozen' + self._update(freeze=True) + self._status = 'frozen' def thaw(self): """Causes the zone to become thawed. Thawing a frozen zone allows changes to again be made to the zone. """ - api_args = {'thaw': True} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) - if response['status'] == 'success': - self._status = 'active' + self._update(thaw=True) + self._status = 'active' def publish(self): """Causes all pending changes to become part of the zone. The serial number increments based on its serial style and the data is pushed out to the nameservers. """ - api_args = {'publish': True} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - self._build(response['data']) + self._update(publish=True) def get_notes(self, offset=None, limit=None): """Generates a report containing the Zone Notes for this :class:`Zone` :param offset: The starting point at which to retrieve the notes :param limit: The maximum number of notes to be retrieved - :return: A :class:`list` of :class:`dict` containing :class:`Zone` Notes + :return: A :class:`list` of :class:`dict` containing :class:`Zone` + Notes """ uri = '/ZoneNoteReport/' api_args = {'zone': self.name} @@ -324,7 +252,7 @@ def get_notes(self, offset=None, limit=None): api_args['offset'] = offset if limit: api_args['limit'] = limit - response = DynectSession.get_session().execute(uri, 'POST', api_args) + response = DynectSession.post(uri, api_args) return response['data'] def add_record(self, name=None, record_type='A', *args, **kwargs): @@ -335,14 +263,14 @@ def add_record(self, name=None, record_type='A', *args, **kwargs): :param record_type: The type of record you would like to add. Valid record_type arguments are: 'A', 'AAAA', 'CERT', 'CNAME', 'DHCID', 'DNAME', 'DNSKEY', 'DS', 'KEY', 'KX', 'LOC', 'IPSECKEY', - 'MX', 'NAPTR', 'PTR', 'PX', 'NSAP', 'RP', 'NS', 'SOA', 'SPF', 'SRV', - and 'TXT'. + 'MX', 'NAPTR', 'PTR', 'PX', 'NSAP', 'RP', 'NS', 'SOA', 'SPF', + 'SRV', and 'TXT'. :param args: Non-keyword arguments to pass to the Record constructor :param kwargs: Keyword arguments to pass to the Record constructor """ fqdn = name + '.' + self.name + '.' if name else self.name + '.' # noinspection PyCallingNonCallable - rec = RECS[record_type](self.name, fqdn, *args, **kwargs) + rec = RECORD_TYPES[record_type](self.name, fqdn, *args, **kwargs) if record_type in self.records: self.records[record_type].append(rec) else: @@ -354,8 +282,8 @@ def add_service(self, name=None, service_type=None, *args, **kwargs): zone :param name: The name of the :class:`Node` where this service will be - attached to or `None` to attach it to the root :class:`Node` of this - :class:`Zone` + attached to or `None` to attach it to the root :class:`Node` of + this :class:`Zone` :param service_type: The type of the service you would like to create. Valid service_type arguments are: 'ActiveFailover', 'DDNS', 'DNSSEC', 'DSF', 'GSLB', 'RDNS', 'RTTM', 'HTTPRedirect' @@ -407,18 +335,18 @@ def get_all_records(self): :class:`Zone` """ self.records = {} - uri = '/AllRecord/{}/'.format(self._name) + uri = '/AllRecord/{0}/'.format(self.name) if self.fqdn is not None: - uri += '{}/'.format(self.fqdn) + uri += '{0}/'.format(self.fqdn) api_args = {'detail': 'Y'} - response = DynectSession.get_session().execute(uri, 'GET', api_args) + response = DynectSession.get(uri, api_args) # Strip out empty record_type lists record_lists = {label: rec_list for label, rec_list in response['data'].items() if rec_list != []} records = {} for key, record_list in record_lists.items(): search = key.split('_')[0].upper() - constructor = RECS[search] + constructor = RECORD_TYPES[search] list_records = [] for record in record_list: del record['zone'] @@ -426,9 +354,8 @@ def get_all_records(self): # Unpack rdata for r_key, r_val in record['rdata'].items(): record[r_key] = r_val - record['create'] = False - list_records.append(constructor(self._name, self.fqdn, - **record)) + list_records.append(constructor(self.name, self.fqdn, + api=False, **record)) records[key] = list_records return records @@ -439,8 +366,8 @@ def get_all_records_by_type(self, record_type): :param record_type: The type of :class:`DNSRecord` you wish returned. Valid record_type arguments are: 'A', 'AAAA', 'CERT', 'CNAME', 'DHCID', 'DNAME', 'DNSKEY', 'DS', 'KEY', 'KX', 'LOC', 'IPSECKEY', - 'MX', 'NAPTR', 'PTR', 'PX', 'NSAP', 'RP', 'NS', 'SOA', 'SPF', 'SRV', - and 'TXT'. + 'MX', 'NAPTR', 'PTR', 'PX', 'NSAP', 'RP', 'NS', 'SOA', 'SPF', + 'SRV', and 'TXT'. :return: A :class:`List` of :class:`DNSRecord`'s """ names = {'A': 'ARecord', 'AAAA': 'AAAARecord', 'CERT': 'CERTRecord', @@ -452,10 +379,10 @@ def get_all_records_by_type(self, record_type): 'PX': 'PXRecord', 'NSAP': 'NSAPRecord', 'RP': 'RPRecord', 'NS': 'NSRecord', 'SOA': 'SOARecord', 'SPF': 'SPFRecord', 'SRV': 'SRVRecord', 'TLSA': 'TLSARecord', 'TXT': 'TXTRecord'} - constructor = RECS[record_type] - uri = '/{}/{}/{}/'.format(names[record_type], self._name, self.fqdn) + constructor = RECORD_TYPES[record_type] + uri = '/{0}/{1}/{2}/'.format(names[record_type], self.name, self.fqdn) api_args = {'detail': 'Y'} - response = DynectSession.get_session().execute(uri, 'GET', api_args) + response = DynectSession.get(uri, api_args) records = [] for record in response['data']: del record['fqdn'] @@ -464,8 +391,8 @@ def get_all_records_by_type(self, record_type): for key, val in record['rdata'].items(): record[key] = val del record['rdata'] - record['create'] = False - records.append(constructor(self._name, self.fqdn, **record)) + records.append(constructor(self.name, self.fqdn, api=False, + **record)) return records def get_any_records(self): @@ -475,15 +402,15 @@ def get_any_records(self): if self.fqdn is None: return api_args = {'detail': 'Y'} - uri = '/ANYRecord/{}/{}/'.format(self._name, self.fqdn) - response = DynectSession.get_session().execute(uri, 'GET', api_args) + uri = '/ANYRecord/{0}/{1}/'.format(self.name, self.fqdn) + response = DynectSession.get(uri, api_args) # Strip out empty record_type lists record_lists = {label: rec_list for label, rec_list in response['data'].items() if rec_list != []} records = {} for key, record_list in record_lists.items(): search = key.split('_')[0].upper() - constructor = RECS[search] + constructor = RECORD_TYPES[search] list_records = [] for record in record_list: del record['zone'] @@ -491,9 +418,8 @@ def get_any_records(self): # Unpack rdata for r_key, r_val in record['rdata'].items(): record[r_key] = r_val - record['create'] = False - list_records.append(constructor(self._name, self.fqdn, - **record)) + list_records.append(constructor(self.name, self.fqdn, + api=False, **record)) records[key] = list_records return records @@ -503,14 +429,14 @@ def get_all_active_failovers(self): :return: A :class:`List` of :class:`ActiveFailover` Services """ - uri = '/Failover/{}/'.format(self._name) + uri = '/Failover/{0}/'.format(self.name) api_args = {'detail': 'Y'} - response = DynectSession.get_session().execute(uri, 'GET', api_args) + response = DynectSession.get(uri, api_args) afos = [] for failover in response['data']: del failover['zone'] del failover['fqdn'] - afos.append(ActiveFailover(self._name, self._fqdn, api=False, + afos.append(ActiveFailover(self.name, self.fqdn, api=False, **failover)) return afos @@ -520,14 +446,15 @@ def get_all_ddns(self): :return: A :class:`List` of :class:`DDNS` Services """ - uri = '/DDNS/{}/'.format(self._name) + uri = '/DDNS/{0}/'.format(self.name) api_args = {'detail': 'Y'} - response = DynectSession.get_session().execute(uri, 'GET', api_args) + response = DynectSession.get(uri, api_args) ddnses = [] for ddns in response['data']: del ddns['zone'] del ddns['fqdn'] - ddnses.append(DynamicDNS(self._name, self._fqdn, api=False, **ddns)) + ddnses.append( + DynamicDNS(self.name, self._fqdn, api=False, **ddns)) return ddnses def get_all_httpredirect(self): @@ -536,9 +463,9 @@ def get_all_httpredirect(self): :return: A :class:`List` of :class:`HTTPRedirect` Services """ - uri = '/HTTPRedirect/{}/'.format(self._name) + uri = '/HTTPRedirect/{0}/'.format(self._name) api_args = {'detail': 'Y'} - response = DynectSession.get_session().execute(uri, 'GET', api_args) + response = DynectSession.get(uri, api_args) httpredirs = [] for httpredir in response['data']: del httpredir['zone'] @@ -552,14 +479,14 @@ def get_all_gslb(self): :return: A :class:`List` of :class:`GSLB` Services """ - uri = '/GSLB/{}/'.format(self._name) + uri = '/GSLB/{0}/'.format(self.name) api_args = {'detail': 'Y'} - response = DynectSession.get_session().execute(uri, 'GET', api_args) + response = DynectSession.get(uri, api_args) gslbs = [] for gslb_svc in response['data']: del gslb_svc['zone'] del gslb_svc['fqdn'] - gslbs.append(GSLB(self._name, self._fqdn, api=False, **gslb_svc)) + gslbs.append(GSLB(self.name, self._fqdn, api=False, **gslb_svc)) return gslbs def get_all_rdns(self): @@ -568,14 +495,15 @@ def get_all_rdns(self): :return: A :class:`List` of :class:`ReverseDNS` Services """ - uri = '/IPTrack/{}/'.format(self._name) + uri = '/IPTrack/{0}/'.format(self.name) api_args = {'detail': 'Y'} - response = DynectSession.get_session().execute(uri, 'GET', api_args) + response = DynectSession.get(uri, api_args) rdnses = [] for rdns in response['data']: del rdns['zone'] del rdns['fqdn'] - rdnses.append(ReverseDNS(self._name, self._fqdn, api=False, **rdns)) + rdnses.append( + ReverseDNS(self.name, self._fqdn, api=False, **rdns)) return rdnses def get_all_rttm(self): @@ -584,14 +512,14 @@ def get_all_rttm(self): :return: A :class:`List` of :class:`RTTM` Services """ - uri = '/RTTM/{}/'.format(self._name) + uri = '/RTTM/{0}/'.format(self.name) api_args = {'detail': 'Y'} - response = DynectSession.get_session().execute(uri, 'GET', api_args) + response = DynectSession.get(uri, api_args) rttms = [] for rttm_svc in response['data']: del rttm_svc['zone'] del rttm_svc['fqdn'] - rttms.append(RTTM(self._name, self._fqdn, api=False, **rttm_svc)) + rttms.append(RTTM(self.name, self.fqdn, api=False, **rttm_svc)) return rttms def get_qps(self, start_ts, end_ts=None, breakdown=None, hosts=None, @@ -601,8 +529,8 @@ def get_qps(self, start_ts, end_ts=None, breakdown=None, hosts=None, :param start_ts: datetime.datetime instance identifying point in time for the QPS report - :param end_ts: datetime.datetime instance indicating the end of the data - range for the report. Defaults to datetime.datetime.now() + :param end_ts: datetime.datetime instance indicating the end of the + data range for the report. Defaults to datetime.datetime.now() :param breakdown: By default, most data is aggregated together. Valid values ('hosts', 'rrecs', 'zones'). :param hosts: List of hosts to include in the report. @@ -619,41 +547,36 @@ def get_qps(self, start_ts, end_ts=None, breakdown=None, hosts=None, api_args['hosts'] = hosts if rrecs is not None: api_args['rrecs'] = rrecs - response = DynectSession.get_session().execute('/QPSReport/', - 'POST', api_args) + response = DynectSession.post('/QPSReport/', api_args) return response['data'] - def delete(self): - """Delete this :class:`Zone` and perform nessecary cleanups""" - api_args = {} - DynectSession.get_session().execute(self.uri, 'DELETE', api_args) + def __str__(self): + return force_unicode(': {0}').format(self.name) - def __eq__(self, other): - """Equivalence operations for easily pulling a :class:`Zone` out of a - list of :class:`Zone` objects - """ - if isinstance(other, str): - return other == self._name - elif isinstance(other, Zone): - return other.name == self._name - return False - def __ne__(self, other): - """Non-Equivalence operator""" - return not self.__eq__(other) +class SecondaryZone(DNSAPIObject): + """A class representing DynECT Secondary zones""" + # Secondary Zone URI + uri = '/Secondary/{zone_name}/' - def __str__(self): - """str override""" - return force_unicode(': {}').format(self._name) - __repr__ = __unicode__ = __str__ + #: The name of this secondary zone + zone = StringAttribute('zone') - def __bytes__(self): - """bytes override""" - return bytes(self.__str__()) + #: Alias to zone + name = StringAttribute('zone') + #: A list of IPv4 or IPv6 addresses of the master nameserver(s) for this + #: zone + masters = ListAttribute('masters') + + #: Name of the :class:`~dyn.tm.accounts.Contact` that will receive + #: notifications for this :class:`~dyn.tm.zones.SecondaryZone` + contact_nickname = StringAttribute('contact_nickname') + + #: Name of the TSIG key that will be used to sign transfer requests to this + #: zone's master + tsig_key_name = StringAttribute('tsig_key_name') -class SecondaryZone(object): - """A class representing DynECT Secondary zones""" def __init__(self, zone, *args, **kwargs): """Create a :class:`SecondaryZone` object @@ -665,26 +588,9 @@ def __init__(self, zone, *args, **kwargs): :param tsig_key_name: Name of the TSIG key that will be used to sign transfer requests to this zone's master """ - super(SecondaryZone, self).__init__() - self._zone = self._name = zone - self.uri = '/Secondary/{}/'.format(self._zone) - self._masters = self._contact_nickname = self._tsig_key_name = None - if 'api' in kwargs: - del kwargs['api'] - for key, val in kwargs.items(): - setattr(self, '_' + key, val) - elif len(args) == 0 and len(kwargs) == 0: - self._get() - else: - self._post(*args, **kwargs) - - def _get(self): - """Get a :class:`SecondaryZone` object from the DynECT System""" - api_args = {} - response = DynectSession.get_session().execute(self.uri, 'GET', - api_args) - for key, val in response['data'].items(): - setattr(self, '_' + key, val) + self.uri = self.uri.format(zone_name=zone) + self._zone = zone + super(SecondaryZone, self).__init__(*args, **kwargs) def _post(self, masters, contact_nickname=None, tsig_key_name=None): """Create a new :class:`SecondaryZone` object on the DynECT System""" @@ -696,113 +602,35 @@ def _post(self, masters, contact_nickname=None, tsig_key_name=None): api_args['contact_nickname'] = self._contact_nickname if tsig_key_name: api_args['tsig_key_name'] = self._tsig_key_name - response = DynectSession.get_session().execute(self.uri, 'POST', - api_args) - for key, val in response['data'].items(): - setattr(self, '_' + key, val) - - @property - def zone(self): - """The name of this :class:`SecondaryZone`""" - return self._zone - @zone.setter - def zone(self, value): - pass - - @property - def masters(self): - """A list of IPv4 or IPv6 addresses of the master nameserver(s) for this - zone. - """ - return self._masters - @masters.setter - def masters(self, value): - self._masters = value - api_args = {'masters': self._masters} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - for key, val in response['data'].items(): - setattr(self, '_' + key, val) - - @property - def contact_nickname(self): - """Name of the :class:`Contact` that will receive notifications for this - zone - """ - return self._contact_nickname - @contact_nickname.setter - def contact_nickname(self, value): - self._contact_nickname = value - api_args = {'contact_nickname': self._contact_nickname} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - for key, val in response['data'].items(): - setattr(self, '_' + key, val) - - @property - def tsig_key_name(self): - """Name of the TSIG key that will be used to sign transfer requests to - this zone's master - """ - return self._tsig_key_name - @tsig_key_name.setter - def tsig_key_name(self, value): - self._tsig_key_name = value - api_args = {'tsig_key_name': self._tsig_key_name} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - for key, val in response['data'].items(): - setattr(self, '_' + key, val) + response = DynectSession.post(self.uri, api_args) + self._build(response['data']) def activate(self): """Activates this secondary zone""" - api_args = {'activate': True} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - for key, val in response['data'].items(): - setattr(self, '_' + key, val) + self._update(activate=True) def deactivate(self): """Deactivates this secondary zone""" - api_args = {'deactivate': True} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - for key, val in response['data'].items(): - setattr(self, '_' + key, val) + self._update(deactivate=True) def retransfer(self): """Retransfers this secondary zone from its original provider into Dyn's Managed DNS """ - api_args = {'retransfer': True} - response = DynectSession.get_session().execute(self.uri, 'PUT', - api_args) - for key, val in response['data'].items(): - setattr(self, '_' + key, val) - - def delete(self): - """Delete this :class:`SecondaryZone`""" - api_args = {} - uri = '/Zone/{}/'.format(self._zone) - DynectSession.get_session().execute(uri, 'DELETE', api_args) + self._update(retransfer=True) def __str__(self): - """str override""" - return force_unicode(': {}').format(self._zone) - __repr__ = __unicode__ = __str__ - - def __bytes__(self): - """bytes override""" - return bytes(self.__str__()) + return force_unicode(': {0}').format(self.zone) class Node(object): """Node object. Represents a valid fqdn node within a zone. It should be noted that simply creating a :class:`Node` object does not actually create - anything on the DynECT System. The only way to actively create a + anything on the DynECT System. The only way to really create a :class:`Node` on the DynECT System is by attaching either a record or a service to it. """ + def __init__(self, zone, fqdn=None): """Create a :class:`Node` object @@ -821,13 +649,13 @@ def add_record(self, record_type='A', *args, **kwargs): :param record_type: The type of record you would like to add. Valid record_type arguments are: 'A', 'AAAA', 'CERT', 'CNAME', 'DHCID', 'DNAME', 'DNSKEY', 'DS', 'KEY', 'KX', 'LOC', 'IPSECKEY', - 'MX', 'NAPTR', 'PTR', 'PX', 'NSAP', 'RP', 'NS', 'SOA', 'SPF', 'SRV', - and 'TXT'. + 'MX', 'NAPTR', 'PTR', 'PX', 'NSAP', 'RP', 'NS', 'SOA', 'SPF', + 'SRV', and 'TXT'. :param args: Non-keyword arguments to pass to the Record constructor :param kwargs: Keyword arguments to pass to the Record constructor """ # noinspection PyCallingNonCallable - rec = RECS[record_type](self.zone, self.fqdn, *args, **kwargs) + rec = RECORD_TYPES[record_type](self.zone, self.fqdn, *args, **kwargs) if record_type in self.records: self.records[record_type].append(rec) else: @@ -843,17 +671,17 @@ def add_service(self, service_type=None, *args, **kwargs): :param args: Non-keyword arguments to pass to the Record constructor :param kwargs: Keyword arguments to pass to the Record constructor """ - constructors = {'ActiveFailover': ActiveFailover, - 'DDNS': DynamicDNS, - 'DNSSEC': DNSSEC, - 'DSF': TrafficDirector, - 'GSLB': GSLB, - 'RDNS': ReverseDNS, - 'RTTM': RTTM, - 'HTTPRedirect': HTTPRedirect} + objects = {'ActiveFailover': ActiveFailover, + 'DDNS': DynamicDNS, + 'DNSSEC': DNSSEC, + 'DSF': TrafficDirector, + 'GSLB': GSLB, + 'RDNS': ReverseDNS, + 'RTTM': RTTM, + 'HTTPRedirect': HTTPRedirect} + # noinspection PyCallingNonCallable - service = constructors[service_type](self.zone, self.fqdn, *args, - **kwargs) + service = objects[service_type](self.zone, self.fqdn, *args, **kwargs) self.services.append(service) return service @@ -863,18 +691,18 @@ def get_all_records(self): point on the zone hierarchy """ self.records = {} - uri = '/AllRecord/{}/'.format(self.zone) + uri = '/AllRecord/{0}/'.format(self.zone) if self.fqdn is not None: - uri += '{}/'.format(self.fqdn) + uri += '{0}/'.format(self.fqdn) api_args = {'detail': 'Y'} - response = DynectSession.get_session().execute(uri, 'GET', api_args) + response = DynectSession.get(uri, api_args) # Strip out empty record_type lists record_lists = {label: rec_list for label, rec_list in response['data'].items() if rec_list != []} records = {} for key, record_list in record_lists.items(): search = key.split('_')[0].upper() - constructor = RECS[search] + constructor = RECORD_TYPES[search] list_records = [] for record in record_list: del record['zone'] @@ -882,8 +710,8 @@ def get_all_records(self): # Unpack rdata for r_key, r_val in record['rdata'].items(): record[r_key] = r_val - record['create'] = False - list_records.append(constructor(self.zone, self.fqdn, **record)) + list_records.append( + constructor(self.zone, self.fqdn, api=False, **record)) records[key] = list_records return records @@ -894,8 +722,8 @@ def get_all_records_by_type(self, record_type): :param record_type: The type of :class:`DNSRecord` you wish returned. Valid record_type arguments are: 'A', 'AAAA', 'CERT', 'CNAME', 'DHCID', 'DNAME', 'DNSKEY', 'DS', 'KEY', 'KX', 'LOC', 'IPSECKEY', - 'MX', 'NAPTR', 'PTR', 'PX', 'NSAP', 'RP', 'NS', 'SOA', 'SPF', 'SRV', - and 'TXT'. + 'MX', 'NAPTR', 'PTR', 'PX', 'NSAP', 'RP', 'NS', 'SOA', 'SPF', + 'SRV', and 'TXT'. :return: A list of :class:`DNSRecord`'s """ names = {'A': 'ARecord', 'AAAA': 'AAAARecord', 'CERT': 'CERTRecord', @@ -906,12 +734,11 @@ def get_all_records_by_type(self, record_type): 'MX': 'MXRecord', 'NAPTR': 'NAPTRRecord', 'PTR': 'PTRRecord', 'PX': 'PXRecord', 'NSAP': 'NSAPRecord', 'RP': 'RPRecord', 'NS': 'NSRecord', 'SOA': 'SOARecord', 'SPF': 'SPFRecord', - 'SRV': 'SRVRecord', 'TLSA': TLSARecord, 'TXT': 'TXTRecord'} - constructor = RECS[record_type] - uri = '/{}/{}/{}/'.format(names[record_type], self.zone, - self.fqdn) + 'SRV': 'SRVRecord', 'TLSA': 'TLSARecord', 'TXT': 'TXTRecord'} + constructor = RECORD_TYPES[record_type] + uri = '/{0}/{1}/{2}/'.format(names[record_type], self.zone, self.fqdn) api_args = {'detail': 'Y'} - response = DynectSession.get_session().execute(uri, 'GET', api_args) + response = DynectSession.get(uri, api_args) records = [] for record in response['data']: del record['fqdn'] @@ -920,7 +747,6 @@ def get_all_records_by_type(self, record_type): for key, val in record['rdata'].items(): record[key] = val del record['rdata'] - record['create'] = False records.append(constructor(self.zone, self.fqdn, **record)) return records @@ -929,15 +755,15 @@ def get_any_records(self): if self.fqdn is None: return api_args = {'detail': 'Y'} - uri = '/ANYRecord/{}/{}/'.format(self.zone, self.fqdn) - response = DynectSession.get_session().execute(uri, 'GET', api_args) + uri = '/ANYRecord/{0}/{1}/'.format(self.zone, self.fqdn) + response = DynectSession.get(uri, api_args) # Strip out empty record_type lists record_lists = {label: rec_list for label, rec_list in response['data'].items() if rec_list != []} records = {} for key, record_list in record_lists.items(): search = key.split('_')[0].upper() - constructor = RECS[search] + constructor = RECORD_TYPES[search] list_records = [] for record in record_list: del record['zone'] @@ -945,8 +771,8 @@ def get_any_records(self): # Unpack rdata for r_key, r_val in record['rdata'].items(): record[r_key] = r_val - record['create'] = False - list_records.append(constructor(self.zone, self.fqdn, **record)) + list_records.append( + constructor(self.zone, self.fqdn, **record)) records[key] = list_records return records @@ -954,14 +780,8 @@ def delete(self): """Delete this node, any records within this node, and any nodes underneath this node """ - uri = '/Node/{}/{}'.format(self.zone, self.fqdn) - DynectSession.get_session().execute(uri, 'DELETE', {}) + uri = '/Node/{0}/{1}'.format(self.zone, self.fqdn) + DynectSession.delete(uri) def __str__(self): - """str override""" - return force_unicode(': {}').format(self.fqdn) - __repr__ = __unicode__ = __str__ - - def __bytes__(self): - """bytes override""" - return bytes(self.__str__()) + return force_unicode(': {0}').format(self.fqdn)