You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by el...@apache.org on 2019/01/23 16:08:08 UTC

[trafficcontrol] branch master updated: ORT.py now uses the official ATC client

This is an automated email from the ASF dual-hosted git repository.

elsloo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new c90e9ce  ORT.py now uses the official ATC client
c90e9ce is described below

commit c90e9ce4d15b0d2cf21cb2e16585b33bbacf9751
Author: ocket8888 <oc...@gmail.com>
AuthorDate: Mon Jan 14 11:54:36 2019 -0700

    ORT.py now uses the official ATC client
---
 docs/source/api/cachegroups_id.rst                 |   2 +-
 docs/source/api/caches_stats.rst                   |   2 +-
 docs/source/api/cdns_id_queue_update.rst           |   2 +-
 ... => deliveryservices_id_unassigned_servers.rst} |   0
 docs/source/api/deliveryservices_xmlid_servers.rst |   2 +-
 .../api/deliveryservices_xmlid_xmlid_sslkeys.rst   |   2 +-
 ..._id_user_id.rst => federations_id_users_id.rst} |   0
 docs/source/api/isos.rst                           |   2 +-
 infrastructure/cdn-in-a-box/cache/Dockerfile       |   5 +-
 infrastructure/cdn-in-a-box/ort/setup.py           |   2 +-
 .../cdn-in-a-box/ort/traffic_ops_ort/__init__.py   |   9 +-
 .../ort/traffic_ops_ort/config_files.py            |  40 +-
 .../ort/traffic_ops_ort/configuration.py           | 109 ++---
 .../ort/traffic_ops_ort/main_routines.py           |  95 +++--
 .../cdn-in-a-box/ort/traffic_ops_ort/packaging.py  |  22 +-
 .../cdn-in-a-box/ort/traffic_ops_ort/to_api.py     | 465 ++++++++++-----------
 .../clients/python/trafficops/__version__.py       |   2 +-
 .../clients/python/trafficops/restapi.py           | 165 ++------
 .../clients/python/trafficops/tosession.py         |  88 ++--
 19 files changed, 444 insertions(+), 570 deletions(-)

diff --git a/docs/source/api/cachegroups_id.rst b/docs/source/api/cachegroups_id.rst
index 0a4d34f..bbf7725 100644
--- a/docs/source/api/cachegroups_id.rst
+++ b/docs/source/api/cachegroups_id.rst
@@ -13,7 +13,7 @@
 .. limitations under the License.
 ..
 
-.. _to-api-cachegroups_id:
+.. _to-api-cachegroups-id:
 
 **********************
 ``cachegroups/{{ID}}``
diff --git a/docs/source/api/caches_stats.rst b/docs/source/api/caches_stats.rst
index e9d0185..ea3195c 100644
--- a/docs/source/api/caches_stats.rst
+++ b/docs/source/api/caches_stats.rst
@@ -14,7 +14,7 @@
 ..
 
 
-.. _to-api-caches_stats:
+.. _to-api-caches-stats:
 
 ****************
 ``caches/stats``
diff --git a/docs/source/api/cdns_id_queue_update.rst b/docs/source/api/cdns_id_queue_update.rst
index ea1ff58..a83c79c 100644
--- a/docs/source/api/cdns_id_queue_update.rst
+++ b/docs/source/api/cdns_id_queue_update.rst
@@ -13,7 +13,7 @@
 .. limitations under the License.
 ..
 
-.. _to-cdns-id-queue_update:
+.. _to-api-cdns-id-queue_update:
 
 ****************************
 ``cdns/{{ID}}/queue_update``
diff --git a/docs/source/api/deliveryservices_id_servers_unassigned.rst b/docs/source/api/deliveryservices_id_unassigned_servers.rst
similarity index 100%
rename from docs/source/api/deliveryservices_id_servers_unassigned.rst
rename to docs/source/api/deliveryservices_id_unassigned_servers.rst
diff --git a/docs/source/api/deliveryservices_xmlid_servers.rst b/docs/source/api/deliveryservices_xmlid_servers.rst
index 5cdf20f..bcc0ef9 100644
--- a/docs/source/api/deliveryservices_xmlid_servers.rst
+++ b/docs/source/api/deliveryservices_xmlid_servers.rst
@@ -13,7 +13,7 @@
 .. limitations under the License.
 ..
 
-.. _to-api-deliveryservices-xml_id-servers:
+.. _to-api-deliveryservices-xmlid-servers:
 
 ***************************************
 ``deliveryservices/{{xml_id}}/servers``
diff --git a/docs/source/api/deliveryservices_xmlid_xmlid_sslkeys.rst b/docs/source/api/deliveryservices_xmlid_xmlid_sslkeys.rst
index f5b2372..ab13f26 100644
--- a/docs/source/api/deliveryservices_xmlid_xmlid_sslkeys.rst
+++ b/docs/source/api/deliveryservices_xmlid_xmlid_sslkeys.rst
@@ -13,7 +13,7 @@
 .. limitations under the License.
 ..
 
-.. to-api-deliveryservices-xmlid-xmlid-sslkeys
+.. _to-api-deliveryservices-xmlid-xmlid-sslkeys:
 
 ********************************************
 ``deliveryservices/xmlId/{{XMLID}}/sslkeys``
diff --git a/docs/source/api/federations_id_user_id.rst b/docs/source/api/federations_id_users_id.rst
similarity index 100%
rename from docs/source/api/federations_id_user_id.rst
rename to docs/source/api/federations_id_users_id.rst
diff --git a/docs/source/api/isos.rst b/docs/source/api/isos.rst
index 21ac008..4d9cf2b 100644
--- a/docs/source/api/isos.rst
+++ b/docs/source/api/isos.rst
@@ -13,7 +13,7 @@
 .. limitations under the License.
 ..
 
-.. _to-api-iso:
+.. _to-api-isos:
 
 ********
 ``isos``
diff --git a/infrastructure/cdn-in-a-box/cache/Dockerfile b/infrastructure/cdn-in-a-box/cache/Dockerfile
index c75efcb..2343d7f 100644
--- a/infrastructure/cdn-in-a-box/cache/Dockerfile
+++ b/infrastructure/cdn-in-a-box/cache/Dockerfile
@@ -49,9 +49,10 @@ RUN mkdir -p /var/trafficserver /opt/ort && \
 RUN setcap CAP_NET_BIND_SERVICE=+eip /bin/traffic_server && setcap CAP_NET_BIND_SERVICE=+eip /bin/traffic_manager && setcap CAP_NET_BIND_SERVICE=+eip /bin/trafficserver && setcap CAP_NET_BIND_SERVICE=+eip /bin/traffic_cop
 
 ADD infrastructure/cdn-in-a-box/ort /opt/ort/
+ADD traffic_control/clients/python /opt/Apache-TrafficControl/
 
-WORKDIR /opt/ort
+WORKDIR /opt
 
-RUN touch /var/log/ort.log && pip3 install . && cp traffic_ops_ort.crontab /etc/cron.d/traffic_ops_ort-cron-template
+RUN touch /var/log/ort.log && pip3 install ./Apache-TrafficControl && pip3 install ./ort && cp ort/traffic_ops_ort.crontab /etc/cron.d/traffic_ops_ort-cron-template
 
 CMD exit
diff --git a/infrastructure/cdn-in-a-box/ort/setup.py b/infrastructure/cdn-in-a-box/ort/setup.py
index 96b513c..84569fc 100755
--- a/infrastructure/cdn-in-a-box/ort/setup.py
+++ b/infrastructure/cdn-in-a-box/ort/setup.py
@@ -60,7 +60,7 @@ setup(
 	],
 	keywords='network connection configuration TrafficControl',
 	packages=find_packages(exclude=['contrib', 'docs', 'tests']),
-	install_requires=['setuptools', 'typing', 'requests', 'urllib3', 'distro', 'psutil'],
+	install_requires=['setuptools', 'typing', 'requests', 'urllib3', 'distro', 'psutil', 'Apache-TrafficControl'],
 	# data_files=[('etc/crontab', ['traffic_ops_ort.crontab'])],
 	entry_points={
 		'console_scripts': [
diff --git a/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/__init__.py b/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/__init__.py
index 1083f32..a6a37c5 100644
--- a/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/__init__.py
+++ b/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/__init__.py
@@ -23,7 +23,7 @@ exactly like that legacy script, with the ability to set system configuration
 files and start, stop, and restart HTTP cache servers etc.
 """
 
-__version__ = "0.0.2"
+__version__ = "0.0.3"
 __author__  = "Brennan Fieck"
 
 import argparse
@@ -66,14 +66,15 @@ def doMain(args:argparse.Namespace) -> int:
 		logging.critical("Malformed or invalid Traffic_Ops_URL: '%s'", args.Traffic_Ops_URL)
 		return 1
 
-	logging.info("Traffic Ops URL '%s' set and verified", configuration.TO_URL)
+	logging.info("Traffic Ops URL 'https://%s:%d' set and verified",
+	                    configuration.TO_HOST, configuration.TO_PORT)
 
 	if not configuration.setTOCredentials(args.Traffic_Ops_Login):
 		logging.critical("Traffic Ops login credentials invalid or incorrect.")
 		return 1
 
-	logging.info("Got TO Cookie - valid until %s",
-	             datetime.datetime.fromtimestamp(configuration.TO_COOKIE.expires))
+	#logging.info("Got TO Cookie - valid until %s",
+	#             datetime.datetime.fromtimestamp(configuration.TO_COOKIE.expires))
 
 	configuration.WAIT_FOR_PARENTS = args.wait_for_parents
 
diff --git a/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/config_files.py b/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/config_files.py
index 3e52db3..c1f1dca 100644
--- a/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/config_files.py
+++ b/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/config_files.py
@@ -60,13 +60,14 @@ class ConfigFile():
 		...             "scope": "servers"}))
 		ConfigFile(path='/path/to/test', URI='http://test', scope='servers')
 		"""
-		from .configuration import TO_URL
+		from .configuration import TO_HOST, TO_PORT, TO_USE_SSL
 
 		try:
 			self.fname = raw["fnameOnDisk"]
 			self.location = raw["location"]
 			if "apiUri" in raw:
-				self.URI = '/'.join((TO_URL, raw["apiUri"].lstrip('/')))
+				self.URI = "https://" if TO_USE_SSL else "http://"
+				self.URI = "%s%s:%d/%s" % (self.URI, TO_HOST, TO_PORT, raw["apiUri"].lstrip('/'))
 			else:
 				self.URI = raw["url"]
 			self.scope = raw["scope"]
@@ -88,42 +89,19 @@ class ConfigFile():
 		return "ConfigFile(path=%r, URI=%r, scope=%r)" %\
 		          (self.path, self.URI if self.URI else None, self.scope)
 
-	def __str__(self) -> str:
-		"""
-		Implements ``str(self)``
-
-		:returns: The contents of the file, fetched using :meth:`fetchContents` if necessary
-		"""
-		if self.contents:
-			return self.contents
-
-		try:
-			self.fetchContents()
-		except ConnectionError as e:
-			logging.debug("%s", e, exc_info=True, stack_info=True)
-
-		return self.contents
-
-	def fetchContents(self):
+	def fetchContents(self, api:'to_api.API'):
 		"""
 		Fetches the file contents from :attr:`URI` if possible. Sets :attr:`contents` when
 		successful.
 
+		:param api: A valid, authenticated API session for use when interacting with Traffic Ops
 		:raises ConnectionError: if something goes wrong fetching the file contents from Traffic
 			Ops
 		"""
-		from . import configuration as conf, utils
 		logging.info("Fetching file %s", self.fname)
 
 		try:
-			# You can't really check this any earlier, since even a 'url' field could, potentially,
-			# point at Traffic Ops
-			if self.URI.startswith(conf.TO_URL):
-				cookies = {conf.TO_COOKIE.name: conf.TO_COOKIE.value}
-			else:
-				cookies = None
-
-			self.contents = utils.getTextResponse(self.URI, cookies=cookies, verify=conf.VERIFY)
+			self.contents = api.getRaw(self.URI)
 		except ValueError as e:
 			raise ConnectionError from e
 
@@ -161,17 +139,19 @@ class ConfigFile():
 		logging.info("Backup File written")
 
 
-	def update(self):
+	def update(self, api:'to_api.API'):
 		"""
 		Updates the file if required, backing up as necessary
 
+		:param api: A valid, authenticated API session for use when interacting with Traffic Ops
 		:raises OSError: when reading/writing files fails for some reason
 		"""
 		from . import utils
 		from .configuration import MODE, Modes, SERVER_INFO
 		from .services import NEEDED_RELOADS, FILES_THAT_REQUIRE_RELOADS
 
-		finalContents = sanitizeContents(str(self))
+		self.fetchContents(api)
+		finalContents = sanitizeContents(self.contents)
 		# Ensure POSIX-compliant files
 		if not finalContents.endswith('\n'):
 			finalContents += '\n'
diff --git a/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/configuration.py b/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/configuration.py
index 15c3497..6f51990 100644
--- a/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/configuration.py
+++ b/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/configuration.py
@@ -22,12 +22,10 @@ hold and set up the log level, run modes, Traffic Ops login
 credentials etc.
 """
 
-import datetime
 import enum
 import logging
 import os
 import platform
-import typing
 
 import distro
 import requests
@@ -167,12 +165,19 @@ def setTSRoot(tsroot:str) -> bool:
 	return True
 
 
-#: Holds the full URL including schema (e.g. 'http') and port that points at Traffic Ops
-TO_URL = None
+#: :const:`True` if Traffic Ops communicates using SSL, :const:`False` otherwise
+TO_USE_SSL = False
+
+#: Holds only the :abbr:`FQDN (Fully Quallified Domain Name)` of the Traffic Ops server
+TO_HOST = None
+
+#: Holds the port number on which the Traffic Ops server listens for incoming HTTP(S) requests
+TO_PORT = None
 
 def setTOURL(url:str) -> bool:
 	"""
-	Sets the :data:`TO_URL` global variable and verifies it
+	Sets the :data:`TO_USE_SSL`, :data:`TO_PORT` and :data:`TO_HOST` global variables and verifies,
+	them.
 
 	:param url: A full URL (including schema - and port when necessary) specifying the location of
 		a running Traffic Ops server
@@ -190,84 +195,60 @@ def setTOURL(url:str) -> bool:
 	except (AttributeError, ValueError) as e:
 		raise ValueError from e
 
-	global TO_URL
-	TO_URL = url
+	global TO_HOST, TO_PORT, TO_USE_SSL
 
-	return True
+	port = None
+
+	if url.lower().startswith("http://"):
+		url = url[7:]
+		port = 80
+		TO_USE_SSL = False
+	elif url.lower().startswith("https://"):
+		url = url[8:]
+		port = 443
+		TO_USE_SSL = True
 
+	# I'm assuming here that a valid FQDN won't include ':' - and it shouldn't.
+	portpoint = url.find(':')
+	if portpoint > 0:
+		TO_HOST = url[:portpoint]
+		port = int(url[portpoint+1:])
+	else:
+		TO_HOST = url
 
-#: Holds a Mojolicious cookie for validating connections to Traffic Ops
-TO_COOKIE = None
+	if port is None:
+		raise ValueError("Couldn't determine port number from URL '%s'!" % url)
+
+	TO_PORT = port
+
+	return True
 
-#: Holds the login information for re-obtaining a cookie when the one in :data:`TO_COOKIE` expires
-TO_LOGIN = None
+#: Holds the username used for logging in to Traffic Ops
+USERNAME = None
+
+#: Holds the password used to authenticate :data:`USERNAME` with Traffic Ops
+PASSWORD = None
 
 def setTOCredentials(login:str) -> bool:
 	"""
 	Parses and returns a JSON-encoded login string for the Traffic Ops API.
-	This will set :data:`TO_COOKIE` and :data:`TO_LOGIN` if login is successful.
+	This will set :data:`USERNAME` and :data:`PASSWORD` if login is successful.
 
 	:param login: The raw login info as passed on the command line (e.g. 'username:password')
 	:raises ValueError: if ``login`` is not a :const:`str`.
 	:returns: whether or not the login could be set and validated successfully
 	"""
 	try:
-		login = '{{"u": "{0}", "p": "{1}"}}'.format(*login.split(':'))
+		u, p = login.split(':')
+		login = '{{"u": "{0}", "p": "{1}"}}'.format(u, p)
 	except IndexError:
 		logging.critical("Bad Traffic_Ops_Login: '%s' - should be like 'username:password'", login)
 		return False
 	except (AttributeError, ValueError) as e:
 		raise ValueError from e
 
-	global TO_LOGIN
-	TO_LOGIN = login
-
-	try:
-		getNewTOCookie()
-	except PermissionError:
-		return False
+	global USERNAME, PASSWORD
+	USERNAME = u
+	PASSWORD = p
 
 	return True
-
-def getNewTOCookie():
-	"""
-	Re-obtains a cookie from Traffic Ops based on the login credentials in :data:`TO_LOGIN`
-
-	:raises PermissionError: if :data:`TO_LOGIN` or :data:`TO_URL` are unset, invalid,
-		or the wrong type
-	"""
-	global TO_URL, TO_LOGIN, VERIFY, TO_COOKIE
-	if TO_URL is None or not isinstance(TO_URL, str) or\
-	   TO_LOGIN is None or not isinstance(TO_LOGIN, str):
-		raise PermissionError("TO_URL and TO_LOGIN must be set prior to calling this function!")
-
-	try:
-		# Obtain login cookie
-		cookie = requests.post(TO_URL + '/api/1.3/user/login', data=TO_LOGIN, verify=VERIFY)
-	except requests.exceptions.RequestException as e:
-		logging.critical("Login credentials rejected by Traffic Ops")
-		raise PermissionError from e
-
-	if not cookie.cookies or 'mojolicious' not in cookie.cookies:
-		logging.error("Response code: %d", cookie.status_code)
-		logging.warning("Response Headers: %s", cookie.headers)
-		logging.debug("Response: %s", cookie.content)
-		raise PermissionError("Login credentials rejected by Traffic Ops")
-
-	TO_COOKIE = [c for c in cookie.cookies if c.name == "mojolicious"][0]
-
-def getTOCookie() -> typing.Dict[str, str]:
-	"""
-	A small, convenience wrapper for getting a current, valid Traffic Ops authentication cookie. If
-	:data:`TO_COOKIE` is expired, this function requests a new one from Traffic Ops
-
-	:returns: A cookie dataset that may be passed directly to :mod:`requests` functions
-	:raises PermissionError: if :data:`TO_LOGIN` and/or :data:`TO_URL` are unset, invalid, or the
-		wrong type
-	"""
-	global TO_COOKIE
-
-	if datetime.datetime.now().timestamp() >= TO_COOKIE.expires:
-		getNewTOCookie()
-
-	return {TO_COOKIE.name: TO_COOKIE.value}
diff --git a/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/main_routines.py b/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/main_routines.py
index d95b7c4..cda4a54 100644
--- a/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/main_routines.py
+++ b/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/main_routines.py
@@ -24,6 +24,10 @@ import os
 import logging
 import requests
 
+from trafficops.restapi import LoginError, OperationError
+
+from . import to_api
+
 #: A constant that holds the absolute path to the status file directory
 STATUS_FILE_DIR = "/opt/ort/status"
 
@@ -31,18 +35,20 @@ class ORTException(Exception):
 	"""Signifies an ORT related error"""
 	pass
 
-def syncDSState() -> bool:
+def syncDSState(api:to_api.API) -> bool:
 	"""
-	Queries Traffic Ops for the Delivery Service's sync state
+	Queries Traffic Ops for the :term:`Delivery Service`'s sync state
+
+	:param api: A :class:`traffic_ops_ort.to_api.API` object to use when interacting with Traffic Ops
 
 	:raises ORTException: when something goes wrong
 	:returns: whether or not an update is needed
 	"""
-	from . import to_api, configuration
+	from . import configuration
 	logging.info("starting syncDS state fetch")
 
 	try:
-		updateStatus = to_api.getUpdateStatus(configuration.HOSTNAME[0])[0]
+		updateStatus = api.getUpdateStatus(api.hostname)[0]
 	except (IndexError, ConnectionError, requests.exceptions.RequestException) as e:
 		logging.critical("Server configuration not found in Traffic Ops!")
 		raise ORTException from e
@@ -54,18 +60,20 @@ def syncDSState() -> bool:
 
 	return 'upd_pending' in updateStatus and updateStatus['upd_pending']
 
-def revalidateState() -> bool:
+def revalidateState(api:to_api.API) -> bool:
 	"""
 	Checks the revalidation status of this server in Traffic Ops
 
+	:param api: A :class:`traffic_ops_ort.to_api.API` object to use when interacting with Traffic Ops
+
 	:returns: whether or not this server has a revalidation pending
 	:raises ORTException:
 	"""
-	from . import to_api, configuration as conf
+	from . import configuration as conf
 	logging.info("starting revalidation state fetch")
 
 	try:
-		to_api.getUpdateStatus(conf.HOSTNAME[0])[0]
+		updateStatus = api.getUpdateStatus(api.hostname)
 	except (IndexError, ConnectionError, requests.exceptions.RequestException) as e:
 		logging.critical("Server configuration not found in Traffic Ops!")
 		raise ORTException from e
@@ -74,33 +82,36 @@ def revalidateState() -> bool:
 		raise ORTException from e
 
 	logging.debug("Retrieved raw revalidation status: %r", updateStatus)
-	if conf.WAIT_FOR_PARENTS and "parent_reval_pending" in updateStatus and updateStatus["parent_reval_pending"]:
+	if conf.WAIT_FOR_PARENTS and\
+	   "parent_reval_pending" in updateStatus and\
+	   updateStatus["parent_reval_pending"]:
 		logging.info("Parent revalidation is pending - waiting for parent")
 		return False
 
 	return "reval_pending" in updateStatus and updateStatus["reval_pending"]
 
-def deleteOldStatusFiles(myStatus:str):
+def deleteOldStatusFiles(myStatus:str, api:to_api.API):
 	"""
 	Attempts to delete any and all old status files
 
 	:param myStatus: the current status - files by this name will not be deleted
+	:param api: A :class:`traffic_ops_ort.to_api.API` object to use when interacting with Traffic Ops
 	:raises ConnectionError: if there's an issue retrieving a list of statuses from
 		Traffic Ops
 	:raises OSError: if a file cannot be deleted for any reason
 	"""
 	from .configuration import MODE, Modes
-	from . import to_api, utils
+	from . import utils
 
 	logging.info("Deleting old status files (those that are not %s)", myStatus)
 
 	doDeleteFiles = MODE is not Modes.REPORT
 
-	for status in to_api.getStatuses():
+	for status in api.get_statuses()[0]:
 
 		# Only the status name matters
 		try:
-			status = status["name"]
+			status = status.name
 		except KeyError as e:
 			logging.debug("Bad status object: %r", status)
 			logging.debug("Original error: %s", e, exc_info=True, stack_info=True)
@@ -117,18 +128,19 @@ def deleteOldStatusFiles(myStatus:str):
 				logging.warning("Deleting file '%s'!", fname)
 				os.remove(fname)
 
-def setStatusFile() -> bool:
+def setStatusFile(api:to_api.API) -> bool:
 	"""
 	Attempts to set the status file according to this server's reported status in Traffic Ops.
 
 	.. warning:: This will create the directory '/opt/ORTstatus' if it does not exist, and may
 		delete files there without warning!
 
+	:param api: A :class:`traffic_ops_ort.to_api.API` object to use when interacting with Traffic Ops
 	:returns: whether or not the status file could be set properly
 	"""
 	global STATUS_FILE_DIR
 	from .configuration import MODE, Modes
-	from . import to_api, utils
+	from . import utils
 	logging.info("Setting status file")
 
 	if not isinstance(MODE, Modes):
@@ -136,7 +148,7 @@ def setStatusFile() -> bool:
 		return False
 
 	try:
-		myStatus = to_api.getMyStatus()
+		myStatus = api.getMyStatus()
 	except ConnectionError as e:
 		logging.error("Failed to set status file - Traffic Ops connection failed")
 		return False
@@ -157,7 +169,7 @@ def setStatusFile() -> bool:
 				return False
 	else:
 		try:
-			deleteOldStatusFiles(myStatus)
+			deleteOldStatusFiles(myStatus, api)
 		except ConnectionError as e:
 			logging.error("Failed to delete old status files - Traffic Ops connection failed.")
 			logging.debug("%s", e, exc_info=True, stack_info=True)
@@ -183,17 +195,17 @@ def setStatusFile() -> bool:
 
 	return True
 
-def processPackages() -> bool:
+def processPackages(api:to_api.API) -> bool:
 	"""
 	Manages the packages that Traffic Ops reports are required for this server.
 
+	:param api: A :class:`traffic_ops_ort.to_api.API` object to use when interacting with Traffic Ops
 	:returns: whether or not the package processing was successfully completed
 	"""
-	from . import to_api
 	from .configuration import Modes, MODE
 
 	try:
-		myPackages = to_api.getMyPackages()
+		myPackages = api.getMyPackages()
 	except (ConnectionError, PermissionError) as e:
 		logging.error("Failed to fetch package list from Traffic Ops - %s", e)
 		logging.debug("%s", e, exc_info=True, stack_info=True)
@@ -211,20 +223,16 @@ def processPackages() -> bool:
 
 	return True
 
-def processServices() -> bool:
+def processServices(api:to_api.API) -> bool:
 	"""
 	Manages the running processes of the server, according to an ancient system known as 'chkconfig'
 
+	:param api: A :class:`traffic_ops_ort.to_api.API` object to use when interacting with Traffic Ops
 	:returns: whether or not the service processing was completed successfully
 	"""
 	from . import services
-	from .to_api import getMyChkconfig
 
-	chkconfig = getMyChkconfig()
-
-	logging.debug("/ort/<hostname>/chkconfig response: %r", chkconfig)
-
-	for item in chkconfig:
+	for item in api.getMyChkconfig():
 		logging.debug("Processing item %r", item)
 
 		if not services.setServiceStatus(item):
@@ -232,13 +240,14 @@ def processServices() -> bool:
 
 	return True
 
-def processConfigurationFiles() -> bool:
+def processConfigurationFiles(api:to_api.API) -> bool:
 	"""
 	Updates and backs up all of a server's configuration files.
 
+	:param api: A :class:`traffic_ops_ort.to_api.API` object to use when interacting with Traffic Ops
 	:returns: whether or not the configuration changes were successful
 	"""
-	from . import config_files, to_api, configuration
+	from . import config_files, configuration
 
 	try:
 		config_files.initBackupDir()
@@ -249,7 +258,7 @@ def processConfigurationFiles() -> bool:
 		return False
 
 	try:
-		myFiles = to_api.getMyConfigFiles()
+		myFiles = api.getMyConfigFiles()
 	except ConnectionError as e:
 		logging.error("Failed to fetch configuration files - Traffic Ops connection failed! %s",e)
 		logging.debug("%s", e, exc_info=True, stack_info=True)
@@ -263,7 +272,7 @@ def processConfigurationFiles() -> bool:
 		try:
 			file = config_files.ConfigFile(file)
 			logging.info("\n============ Processing File: %s ============", file.fname)
-			file.update()
+			file.update(api)
 			logging.info("\n============================================\n")
 
 		# A bad object could just reflect an inconsistent reply structure from the API, so BADASSes
@@ -289,12 +298,22 @@ def run() -> int:
 
 	:returns: an exit code for the script
 	"""
-	from . import configuration, to_api, utils, services
+	from . import configuration, utils, services
+
+	try:
+		api = to_api.API(configuration.USERNAME, configuration.PASSWORD, configuration.TO_HOST,
+		                 configuration.HOSTNAME[0], configuration.TO_PORT, configuration.VERIFY,
+		                 configuration.TO_USE_SSL)
+	except (LoginError, OperationError) as e:
+		logging.critical("Failed to authenticate with Traffic Ops")
+		logging.error(e)
+		logging.debug("%r", e, exc_info=True, stack_info=True)
+		return 1
 
 	# If this is just a revalidation, then we can exit if there's no revalidation pending
 	if configuration.MODE == configuration.Modes.REVALIDATE:
 		try:
-			updateRequired = revalidateState()
+			updateRequired = revalidateState(api)
 		except ORTException as e:
 			logging.debug("%r", e, exc_info=True, stack_info=True)
 			return 2
@@ -309,20 +328,20 @@ def run() -> int:
 	# changes
 	else:
 		try:
-			updateRequired = syncDSState()
+			updateRequired = syncDSState(api)
 		except ORTException as e:
 			logging.debug("%r", e, exc_info=True, stack_info=True)
 			return 2
 
 		# Bail on failures - unless this script is BADASS!
-		if not setStatusFile():
+		if not setStatusFile(api):
 			if configuration.MODE is not configuration.Modes.BADASS:
 				logging.critical("Failed to set status as specified by Traffic Ops")
 				return 2
 			logging.warning("Failed to set status but we're BADASS, so moving on.")
 
 		logging.info("\nProcessing Packages...")
-		if not processPackages():
+		if not processPackages(api):
 			logging.critical("Failed to process packages")
 			if configuration.MODE is not configuration.Modes.BADASS:
 				return 2
@@ -330,7 +349,7 @@ def run() -> int:
 		logging.info("Done.\n")
 
 		logging.info("\nProcessing Services...")
-		if not processServices():
+		if not processServices(api):
 			logging.critical("Failed to process services.")
 			if configuration.MODE is not configuration.Modes.BADASS:
 				return 2
@@ -340,7 +359,7 @@ def run() -> int:
 
 	# All modes process configuration files
 	logging.info("\nProcessing Configuration Files...")
-	if not processConfigurationFiles():
+	if not processConfigurationFiles(api):
 		logging.critical("Failed to process configuration files.")
 		return 2
 	logging.info("Done.\n")
@@ -350,7 +369,7 @@ def run() -> int:
 		   utils.getYesNoResponse("Update Traffic Ops?", default='Y'):
 
 			logging.info("\nUpdating Traffic Ops...")
-			to_api.updateTrafficOps()
+			api.updateTrafficOps()
 			logging.info("Done.\n")
 		else:
 			logging.warning("Traffic Ops was not notified of changes. You should do this manually.")
diff --git a/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/packaging.py b/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/packaging.py
index bc3ae1d..d19fa24 100644
--- a/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/packaging.py
+++ b/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/packaging.py
@@ -39,12 +39,24 @@ class _MetaPackage(type):
 
 		if DISTRO in {'fedora', 'centos', 'rhel'}:
 			concat = '-'
-			pack.checkInstallList = lambda x: [p for p in subprocess.Popen(["/bin/rpm", "-q", x.name], stdout=subprocess.PIPE).communicate()[0].decode().splitlines() if not p.endswith("is not installed")]
+			pack.checkInstallList = lambda x: [ p
+			                                    for p in
+			                                    subprocess.Popen(["/bin/rpm", "-q", x.name],
+			                                                     stdout=subprocess.PIPE)
+			                                              .communicate()[0].decode().splitlines()
+			                                    if not p.endswith("is not installed")]
 			pack.installArgs = ["/bin/yum", "install", "-y"]
 			pack.uninstallArgs = ["/bin/yum", "remove", "-y"]
+
 		elif DISTRO in {'ubuntu', 'linuxmint', 'debian'}:
 			concat = '='
-			pack.checkInstallList = lambda x: ["{1}={2}".format(*p.split()) for p in subprocess.Popen(["/usr/bin/dpkg", "-l", x.name], stdout=subprocess.PIPE).communicate()[0].decode().splitlines()[5:] if p]
+			pack.checkInstallList = lambda x: [ "{1}={2}".format(*p.split())
+			                                    for p in
+			                                    subprocess.Popen(["/usr/bin/dpkg", "-l", x.name],
+			                                                     stdout=subprocess.PIPE)
+			                                    .communicate()[0].decode().splitlines()[5:]
+			                                    if p ]
+
 			pack.installArgs = ["/usr/bin/apt-get", "install", "-y"]
 			pack.uninstallArgs = ["/usr/bin/apt-get", "purge", "-y"]
 
@@ -87,6 +99,12 @@ class Package(metaclass=_MetaPackage):
 		self.name = pkg["name"]
 		self.version = pkg["version"] if "version" in pkg else ""
 
+		# These are defined in the metaclass based on the host system's Linux distribution, but are
+		# specified here for the benefit of static analysis tools
+		self.checkInstallList = getattr(self, 'checkInstallList', lambda: ())
+		self.installArgs = getattr(self, 'installArgs', None)
+		self.uninstallArgs = getattr(self, 'uninstallArgs', None)
+
 	def __repr__(self) -> str:
 		"""
 		Implements ``repr(self)``
diff --git a/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/to_api.py b/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/to_api.py
index 73b16c6..e4ee08a 100644
--- a/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/to_api.py
+++ b/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/to_api.py
@@ -16,18 +16,223 @@
 # under the License.
 
 """
-This module contains functionality for dealing with the Traffic Ops ReST API
+This module contains functionality for dealing with the Traffic Ops ReST API.
+It extends the class provided by the official Apache Traffic Control Client.
 """
 
-import datetime
 import typing
 import logging
-import requests
+
+from trafficops.tosession import TOSession
 
 from . import packaging
 
-#: Caches update statuses mapped by hostnames
-CACHED_UPDATE_STATUS = {}
+class API(TOSession):
+	"""
+	This class extends :class:`trafficops.tosession.TOSession` to provide some ease-of-use
+	functionality for getting things needed by :term:`ORT`.
+	"""
+
+	#: This should always be the latest API version supported - note this breaks compatability with
+	#: older ATC versions. Go figure.
+	VERSION = "1.4"
+
+	#: Caches update statuses mapped by hostnames
+	CACHED_UPDATE_STATUS = {}
+
+	def __init__(self, username:str, password:str, toHost:str, myHostname:str, port:int = 443,
+	                   verify:bool = True, useSSL:bool = True):
+		"""
+		This not only creates the API session, but log the user in immediately.
+
+		:param username: The name of the user as whom :term:`ORT` will authenticate with Traffic Ops
+		:param password: The password of Traffic Ops user ``username``
+		:param toHost: The :abbr:`FQDN (Fully Qualified Domain Name)` of the Traffic Ops server
+		:param myHostname: The (short) hostname of **this** server
+		:param port: The port number on which Traffic Ops listens for incoming HTTP(S) requests
+		:param verify: If :const:`True` SSL certificates will be verifed, if :const:`False` they
+		               will not and warnings about unverified SSL certificates will be swallowed.
+		:param useSSL: If :const:`True` :term:`ORT` will attempt to communicate with Traffic Ops
+		               using SSL, if :const:`False` it will not. *This setting will be respected
+		               regardless of the passed port number!*
+		:raises trafficops.restapi.LoginError: when authentication with Traffic Ops fails
+		:raises trafficops.restapi.OperationError: when some anonymous error occurs communicating
+		                                           with the Traffic Ops server
+		"""
+		super(API, self).__init__(host_ip=toHost, api_version=self.VERSION, host_port=port,
+		                          verify_cert=verify, ssl=useSSL)
+		self.login(username, password)
+
+		self.hostname = myHostname
+
+	def getRaw(self, path:str) -> str:
+		"""
+		This gets the API response to a "raw" path, meaning it will queried directly without a
+		``/api/1.x`` prefix. Because the output structure of the API response is not known, this
+		returns the response body as an unprocessed string rather than a Python object via e.g.
+		Munch.
+
+		:param path: The raw path on the Traffic Ops server
+		:returns: The API response payload
+		"""
+
+		r = self._session.get(path)
+
+		if r.status_code != 200 and r.status_code != 204:
+			raise ValueError("request for '%s' appears to have failed; reason: %s" % (path, r.reason))
+
+		return r.text
+
+	def getMyPackages(self) -> typing.List[packaging.Package]:
+		"""
+		Fetches a list of the packages specified by Traffic Ops that should exist on this server.
+
+		:returns: all of the packages which this system must have, according to Traffic Ops.
+		:raises ConnectionError: if fetching the package list fails
+		:raises ValueError: if the API endpoint returns a malformed response that can't be parsed
+		"""
+		logging.info("Fetching this server's package list from Traffic Ops")
+
+		# Ah, read-only properties that gut functionality, my favorite.
+		from requests.compat import urljoin
+		tmp = self.api_base_url
+		self._api_base_url = urljoin(self._server_url, '/').rstrip('/') + '/'
+
+		packagesPath = '/'.join(("ort", self.hostname, "packages"))
+		myPackages = self.get(packagesPath)
+		self._api_base_url = tmp
+
+		logging.debug("Raw package response: %s", myPackages[1].text)
+
+		return [packaging.Package(p) for p in myPackages[0]]
+
+	def getMyConfigFiles(self) -> typing.List[dict]:
+		"""
+		Fetches configuration files constructed by Traffic Ops for this server
+
+		.. note:: This function will set the :data:`traffic_ops_ort.configuration.SERVER_INFO`
+			object to an instance of :class:`ServerInfo` with the provided information.
+
+		:returns: A list of constructed config file objects
+		:raises ConnectionError: when something goes wrong communicating with Traffic Ops
+		:raises ValueError: when a response was successfully obtained from the Traffic Ops API, but the
+			response could not successfully be parsed as JSON, or was missing information
+		"""
+		from . import configuration
+
+		logging.info("Fetching list of configuration files from Traffic Ops")
+		myFiles = self.get_server_config_files(host_name=self.hostname)
+
+		logging.debug("Raw response from Traffic Ops: %s", myFiles[1].text)
+		myFiles = myFiles[0]
+
+		try:
+			configuration.SERVER_INFO = ServerInfo(myFiles.info)
+			return myFiles.configFiles
+		except (KeyError, AttributeError) as e:
+			raise ValueError from e
+
+	def updateTrafficOps(self):
+		"""
+		Updates Traffic Ops's knowledge of this server's update status.
+		"""
+		from .configuration import MODE, Modes
+		from .utils import getYesNoResponse as getYN
+
+		if MODE is Modes.INTERACTIVE and not getYN("Update Traffic Ops?", default='Y'):
+			logging.warning("Update will not be performed; you should clear updates manually")
+			return
+
+		logging.info("Updating Traffic Ops")
+
+		if MODE is Modes.REPORT:
+			return
+
+		payload = {"updated": False, "reval_updated": False}
+		response = self._session.post('/'.join((self._server_url.rstrip('/'),
+		                                        "update",
+		                                        self.hostname)
+		                             ), data=payload)
+
+		if response.text:
+			logging.info("Traffic Ops response: %s", response.text)
+
+	def getMyChkconfig(self) -> typing.List[dict]:
+		"""
+		Fetches the 'chkconfig' for this server
+
+		:returns: An iterable list of 'chkconfig' entries
+		:raises ConnectionError: when something goes wrong communicating with Traffic Ops
+		:raises ValueError: when a response was successfully obtained from the Traffic Ops API, but the
+			response could not successfully be parsed as JSON, or was missing information
+		"""
+
+
+		# Ah, read-only properties that gut functionality, my favorite.
+		from requests.compat import urljoin
+		tmp = self.api_base_url
+		self._api_base_url = urljoin(self._server_url, '/').rstrip('/') + '/'
+
+		uri = "ort/%s/chkconfig" % self.hostname
+		logging.info("Fetching chkconfig from %s", uri)
+
+		r = self.get(uri)
+		self._api_base_url = tmp
+		logging.debug("Raw response from Traffic Ops: %s", r[1].text)
+
+		return r[0]
+
+	def getUpdateStatus(self, host:str) -> dict:
+		"""
+		Gets the update status of a server.
+
+		.. note:: If the :data:`self.CACHED_UPDATE_STATUS` cached response is set, this function will
+			default to that object. If it is *not* set, then this function will set it.
+
+		:param host: The (short) hostname of the server to query
+		:raises PermissionError: if a new cookie is required, but fails to be aquired
+		:returns: An object representing the API's response
+		"""
+
+		logging.info("Fetching update status for %s from Traffic Ops", host)
+
+		if host in self.CACHED_UPDATE_STATUS:
+			return self.CACHED_UPDATE_STATUS[host]
+
+		r = self.get_server_update_status(server_name=host)
+		logging.debug("Raw response from Traffic Ops: %s", r[1].text)
+
+		self.CACHED_UPDATE_STATUS[host] = r[0]
+
+		return r[0]
+
+	def getMyStatus(self) -> str:
+		"""
+		Fetches the status of this server as set in Traffic Ops
+
+		:raises ConnectionError: if fetching the status fails
+		:raises ValueError: if the :data:`traffic_ops_ort.configuration.HOSTNAME` is not properly set,
+			or a weird value is stored in the global :data:`CACHED_UPDATE_STATUS` response cache.
+		:returns: the name of the status to which this server is set in the Traffic Ops configuration
+
+		.. note:: If the global :data:`CACHED_UPDATE_STATUS` cached response is set, this function will
+			default to the status provided by that object.
+		"""
+
+
+		logging.info("Fetching server status from Traffic Ops")
+
+		r = self.get_servers(query_params={"hostName": self.hostname})
+
+		logging.debug("Raw response from Traffic Ops: %s", r[1].text)
+
+		r = r[0][0]
+
+		try:
+			return r.status
+		except (IndexError, KeyError, AttributeError) as e:
+			logging.error("Malformed response from Traffic Ops to update status request!")
+			raise ConnectionError from e
 
 #: Caches the names of statuses supported by Traffic Ops
 CACHED_STATUSES = []
@@ -108,253 +313,3 @@ class ServerInfo():
 		# if the tcp port is 80.
 		return fmt.replace("__SERVER_TCP_PORT__", str(self.serverTcpPort)\
 		                                          if self.serverTcpPort != 80 else "")
-
-def TOPost(uri:str, data:dict) -> str:
-	"""
-	POSTs the passed data in a request to the specified API endpoint
-
-	:param uri: The Traffic Ops URL-relative path to an API endpoint, e.g. if the intention is
-		to post to ``https://TO_URL:TO_PORT/api/1.3/users``, this should just be
-		``'api/1.3/users'``
-
-			.. note:: This function will ensure the proper concatenation of the Traffic Ops URL
-				to the request path; callers need not worry about whether the ``uri`` ought to
-				begin with a slash.
-
-	:returns: The Traffic Ops server's response to the POST request - possibly empty - as a UTF-8
-		string
-	:raises ConnectionError: when an error occurs trying to communicate with Traffic Ops
-	"""
-	from . import configuration as conf
-
-	uri = '/'.join((conf.TO_URL, uri.lstrip('/')))
-	logging.info("POSTing %r to %s", data, uri)
-
-	try:
-		resp = requests.post(uri, cookies=conf.getTOCookie(), verify=conf.VERIFY, data=data)
-	except (PermissionError, requests.exceptions.RequestException) as e:
-		raise ConnectionError from e
-
-	logging.debug("Raw response from Traffic Ops: %s\n%s\n%s", resp, resp.headers, resp.content)
-
-	return resp.text
-
-def getTOJSONResponse(uri:str) -> dict:
-	"""
-	A wrapper around :func:`traffic_ops_ort.utils.getJSONResponse` that handles cookies and
-	tacks on the top-level Traffic Ops URL.
-
-	:param uri: The Traffic Ops URL-relative path to a JSON API endpoint, e.g. if the intention
-		is to get ``https://TO_URL:TO_PORT/api/1.3/ping``, this should just be ``'api/1.3/ping'``
-
-			.. note:: This function will ensure the proper concatenation of the Traffic Ops URL
-				to the request path; callers need not worry about whether the ``uri`` ought to
-				begin with a slash.
-
-	:returns: The decoded JSON response as an object
-
-			.. note:: If the API response containes a 'response' object, this function will
-				only return that object. Also, if the API response contains an 'alerts' object,
-				they will be logged appropriately
-
-	:raises ConnectionError: when an error occurs trying to communicate with Traffic Ops
-	:raises ValueError: when the request completes successfully, but the response body
-		does not represent a JSON-encoded object.
-	"""
-	global API_LOGGERS
-	from . import configuration as conf, utils
-
-	uri = '/'.join((conf.TO_URL, uri.lstrip('/')))
-	logging.info("Fetching Traffic Ops API response: %s", uri)
-
-	if datetime.datetime.now().timestamp() >= conf.TO_COOKIE.expires:
-		try:
-			conf.getNewTOCookie()
-		except PermissionError as e:
-			raise ConnectionError from e
-
-	resp = utils.getJSONResponse(uri,
-	                             cookies = {conf.TO_COOKIE.name:conf.TO_COOKIE.value},
-	                             verify = conf.VERIFY)
-
-	if "response" in resp:
-		if "alerts" in resp:
-			for alert in resp["alerts"]:
-				if "level" in alert:
-					msg = alert["text"] if "text" in alert else "Unkown"
-					API_LOGGERS[alert["level"]](msg)
-				elif "text" in alert:
-					logging.warning("Traffic Ops API alert: %s", alert["text"])
-					logging.debug("Weird alert encountered: %r", alert)
-
-
-		return resp["response"]
-
-	return resp
-
-def getUpdateStatus(host:str) -> dict:
-	"""
-	Gets the update status of a server.
-
-	.. note:: If the global :data:`CACHED_UPDATE_STATUS` cached response is set, this function will
-		default to that object. If it is *not* set, then this function will set it.
-
-	:param host: The (short) hostname of the server to query
-	:raises ValueError: if ``host`` is not a :const:`str`
-	:raises PermissionError: if a new cookie is required, but fails to be aquired
-	:returns: An object representing the API's response
-	"""
-	global CACHED_UPDATE_STATUS
-
-	logging.info("Fetching update status for %s from Traffic Ops", host)
-	if not isinstance(host, str):
-		raise ValueError("First argument ('host') must be 'str', not '%s'" % type(host))
-
-	if host in CACHED_UPDATE_STATUS:
-		return CACHED_UPDATE_STATUS[host]
-
-	CACHED_UPDATE_STATUS[host] = getTOJSONResponse("api/1.3/servers/%s/update_status" % host)
-
-	return CACHED_UPDATE_STATUS[host]
-
-def getMyStatus() -> str:
-	"""
-	Fetches the status of this server as set in Traffic Ops
-
-	:raises ConnectionError: if fetching the status fails
-	:raises ValueError: if the :data:`traffic_ops_ort.configuration.HOSTNAME` is not properly set,
-		or a weird value is stored in the global :data:`CACHED_UPDATE_STATUS` response cache.
-	:returns: the name of the status to which this server is set in the Traffic Ops configuration
-
-	.. note:: If the global :data:`CACHED_UPDATE_STATUS` cached response is set, this function will
-		default to the status provided by that object.
-	"""
-	global CACHED_UPDATE_STATUS
-	from .configuration import HOSTNAME
-
-	try:
-		if HOSTNAME[0] in CACHED_UPDATE_STATUS:
-			myStatus = CACHED_UPDATE_STATUS[HOSTNAME[0]]
-			if "status" in CACHED_UPDATE_STATUS[HOSTNAME[0]]:
-				return CACHED_UPDATE_STATUS[HOSTNAME[0]]["status"]
-
-			logging.warning("CACHED_UPDATE_STATUS possibly set improperly")
-			logging.warning("clearing this server's cached entry!")
-			logging.debug("value was %r", myStatus)
-			del CACHED_UPDATE_STATUS[HOSTNAME[0]]
-
-	except (IndexError, KeyError) as e:
-		raise ValueError from e
-
-	myStatus = getUpdateStatus(HOSTNAME[0])
-
-	try:
-		return myStatus[0]["status"]
-	except (IndexError, KeyError) as e:
-		logging.error("Malformed response from Traffic Ops to update status request!")
-		raise ConnectionError from e
-
-def getStatuses() -> typing.Generator[str, None, None]:
-	"""
-	Yields a successive list of statuses supported by Traffic Ops.
-
-	.. note:: This is implemented by iterating the :data:`CACHED_STATUSES` global cache -
-		first populating it if it is empty - and so the validity of its outputs
-		depends on the validity of the data stored therein
-
-	:raises ValueError: if a response from the TO API is successful, but cannot be parsed as
-		JSON
-	:raises TypeError: if :data:`CACHED_STATUSES` is not iterable
-	:raises ConnectionError: if something goes wrong contacting the Traffic Ops API
-	:returns: an iterable generator that yields status names as strings
-	"""
-	global CACHED_STATUSES
-
-	logging.info("Retrieving statuses from Traffic Ops")
-
-	if CACHED_STATUSES:
-		logging.debug("Using cached statuses: %r", CACHED_STATUSES)
-		yield from CACHED_STATUSES
-	else:
-		statuses = getTOJSONResponse("api/1.3/statuses")
-		yield from statuses
-
-def getMyPackages() -> typing.List[packaging.Package]:
-	"""
-	Fetches a list of the packages specified by Traffic Ops that should exist on this server.
-
-	:returns: all of the packages which this system must have, according to Traffic Ops.
-	:raises ConnectionError: if fetching the package list fails
-	:raises ValueError: if the API endpoint returns a malformed response that can't be parsed
-	"""
-	from .configuration import HOSTNAME
-
-	logging.info("Fetching this server's package list from Traffic Ops")
-
-	myPackages=getTOJSONResponse('/'.join(("ort", HOSTNAME[0], "packages")))
-
-	logging.debug("Raw package response: %r", myPackages)
-
-	return [packaging.Package(p) for p in myPackages]
-
-def updateTrafficOps():
-	"""
-	Updates Traffic Ops's knowledge of this server's update status.
-	"""
-	from .configuration import MODE, Modes, HOSTNAME
-	from .utils import getYesNoResponse as getYN
-
-	if MODE is Modes.INTERACTIVE and not getYN("Update Traffic Ops?", default='Y'):
-		logging.warning("Update will not be performed; you should do this manually")
-		return
-
-	logging.info("Updating Traffic Ops")
-
-	if MODE is Modes.REPORT:
-		return
-
-	payload = {"updated": False, "reval_updated": False}
-	response = TOPost("/update/%s" % HOSTNAME[0], payload)
-
-	if response:
-		logging.info("Traffic Ops response: %s", response)
-
-def getMyConfigFiles() -> typing.List[dict]:
-	"""
-	Fetches configuration files constructed by Traffic Ops for this server
-
-	.. note:: This function will set the :data:`traffic_ops_ort.configuration.SERVER_INFO`
-		object to an instance of :class:`ServerInfo` with the provided information.
-
-	:returns: A list of constructed config file objects
-	:raises ConnectionError: when something goes wrong communicating with Traffic Ops
-	:raises ValueError: when a response was successfully obtained from the Traffic Ops API, but the
-		response could not successfully be parsed as JSON, or was missing information
-	"""
-	from . import configuration
-
-	uri = "/api/1.3/servers/%s/configfiles/ats" % configuration.HOSTNAME[0]
-
-	myFiles = getTOJSONResponse(uri)
-
-	try:
-		configuration.SERVER_INFO = ServerInfo(myFiles["info"])
-		return myFiles["configFiles"]
-	except KeyError as e:
-		raise ValueError from e
-
-def getMyChkconfig() -> typing.List[dict]:
-	"""
-	Fetches the 'chkconfig' for this server
-
-	:returns: An iterable list of 'chkconfig' entries
-	:raises ConnectionError: when something goes wrong communicating with Traffic Ops
-	:raises ValueError: when a response was successfully obtained from the Traffic Ops API, but the
-		response could not successfully be parsed as JSON, or was missing information
-	"""
-	from . import configuration
-
-	uri = "/ort/%s/chkconfig" % configuration.HOSTNAME[0]
-	logging.info("Fetching chkconfig from %s", uri)
-
-	return getTOJSONResponse(uri)
diff --git a/traffic_control/clients/python/trafficops/__version__.py b/traffic_control/clients/python/trafficops/__version__.py
index 25d3a37..0b09710 100644
--- a/traffic_control/clients/python/trafficops/__version__.py
+++ b/traffic_control/clients/python/trafficops/__version__.py
@@ -14,4 +14,4 @@
 # limitations under the License.
 #
 
-__version__ = '1.0.0'
+__version__ = '1.1.0'
diff --git a/traffic_control/clients/python/trafficops/restapi.py b/traffic_control/clients/python/trafficops/restapi.py
index 9320f39..cacfe83 100644
--- a/traffic_control/clients/python/trafficops/restapi.py
+++ b/traffic_control/clients/python/trafficops/restapi.py
@@ -83,23 +83,13 @@ def api_request(method_name, api_path, supported_versions):
 	generates the RESTful API endpoint, performs the appropriate call to the endpoint and returns
 	the data to the user.
 
-	:param method_name: A method name defined on the Class, this decorator is decorating, that will
-	                    be called to perform the operation. E.g. 'GET', 'POST', 'PUT', 'DELETE',
-	                    etc. The method_name chosen must have the signature of
-	                    ``<method>(self, api_path, **kwargs)`` e.g.
-	                    ``def get(self, api_path, **kwargs): ...``
-
+	:param method_name: A method name defined on the Class, this decorator is decorating, that will be called to perform the operation. E.g. 'GET', 'POST', 'PUT', 'DELETE', etc. The method_name chosen must have the signature of ``<method>(self, api_path, **kwargs)`` e.g. ``def get(self, api_path, **kwargs): ...``
 	:type method_name: str
-	:param api_path: The path to the API end-point that you want to call which does not
-	                 include the base url e.g. ``user/login``, ``servers``, etc. This string can
-	                 contain substitution parameters as denoted by a valid field_name replacement
-	                 field specification as per :meth:`str.format` e.g. ``cachegroups/{id}`` or
-	                 ``cachegroups/{id:d}``
+	:param api_path: The path to the API end-point that you want to call which does not include the base url e.g. ``user/login``, ``servers``, etc. This string can contain substitution parameters as denoted by a valid field_name replacement field specification as per :meth:`str.format` e.g. ``cachegroups/{id}`` or ``cachegroups/{id:d}``
 	:type api_path: str
 	:param supported_versions: A tuple of API versions that this route supports
 	:type supported_versions: Tuple[str]
-	:return: rtype int: A new function that replaces the original function with a boilerplate
-	                    execution process.
+	:return: rtype int: A new function that replaces the original function with a boilerplate execution process.
 	:rtype: Callable[str, Dict[str, Any]]
 	"""
 
@@ -135,18 +125,14 @@ class RestApiSession(object):
 	             headers=None, verify_cert=True, create_session=False, max_retries=5):
 		"""
 		The class initializer.
+
 		:param host_ip: The dns name or ip address of the RESTful API host to use to talk to the API
 		:type host_ip: str
 		:param host_port: The port to use when contacting the RESTful API
 		:type host_port: int
-		:param api_version: The version of the API to make calls against. If supplied, endpoint
-			version validation will be performed. If supplied as None, no version validation will be
-			performed. :const:`None` is allowed so that non-versioned REST APIs can be implemented.
-
+		:param api_version: The version of the API to make calls against. If supplied, endpoint version validation will be performed. If supplied as None, no version validation will be performed. :const:`None` is allowed so that non-versioned REST APIs can be implemented.
 		:type api_version: Union[str, None]
-		:param api_base_path: The part of the url that is the base path, from the web server root
-			(which may include an API version), for all API endpoints without the server url portion
-			e.g. 'api/', 'api/1.2/'
+		:param api_base_path: The part of the url that is the base path, from the web server root (which may include an API version), for all API endpoints without the server url portion e.g. 'api/', 'api/1.2/'
 
 			.. note:: To specify the base path with the passed ``api_version`` you can specify
 				``api_base_path`` as ``api/{api_version}/`` and the API version will be substituted.
@@ -160,9 +146,7 @@ class RestApiSession(object):
 		:type ssl: bool
 		:param headers: The HTTP headers to use when contacting the RESTful API
 		:type headers: Dict[str, str]
-		:param verify_cert: Should the SSL certificates be verified when contacting the RESTful API.
-			You may want to set this to :const:`False` for systems with self-signed certificates.
-
+		:param verify_cert: Should the SSL certificates be verified when contacting the RESTful API. You may want to set this to :const:`False` for systems with self-signed certificates.
 		:type verify_cert: bool
 		:param create_session: Should a session be created automatically?
 		:type create_session: bool
@@ -229,6 +213,7 @@ class RestApiSession(object):
 	def is_open(self):
 		"""
 		Is the session open to the RESTful API? (Read-only Property)
+
 		:return: :const:`True` if yes, otherwise, :const:`False`
 		:rtype: bool
 		"""
@@ -238,6 +223,7 @@ class RestApiSession(object):
 	def session(self):
 		"""
 		The RESTful API session (Read-only Property)
+
 		:return: The requests session
 		:rtype: :class:`requests.Session`
 		"""
@@ -246,6 +232,7 @@ class RestApiSession(object):
 	def create(self):
 		"""
 		Create the requests.Session to communicate with the RESTful API.
+
 		:return: :const:`None`
 		:rtype: NoneType
 		"""
@@ -263,6 +250,7 @@ class RestApiSession(object):
 	def close(self):
 		"""
 		Close and cleanup the requests Session object.
+
 		:return: :const:`None`
 		:rtype: NoneType
 		"""
@@ -280,9 +268,8 @@ class RestApiSession(object):
 	def server_url(self):
 		"""
 		The URL without the api portion. (read-only)
-		:return: The URL should match '[\\w\\+\\-\\.]+://[\\w\\+\\-\\.]+(:\\d+)?'
-			e.g. 'https://to.somedomain.net' or 'https://to.somedomain.net:443'
 
+		:return: The URL should match '[\\w\\+\\-\\.]+://[\\w\\+\\-\\.]+(:\\d+)?' e.g. 'https://to.somedomain.net' or 'https://to.somedomain.net:443'
 		:rtype: str
 		"""
 
@@ -292,6 +279,7 @@ class RestApiSession(object):
 	def api_version(self):
 		"""
 		Returns the api version. (read-only)
+
 		:return: The api version from which this instance will request endpoints.
 		:rtype: str
 		"""
@@ -302,8 +290,8 @@ class RestApiSession(object):
 	def api_base_url(self):
 		"""
 		Returns the base URL. (read-only)
-		:return: The base URL should match '[\\w\\+\\-\\.]+://[\\w\\+\\-\\.]+(:\\d+)?'
-			e.g. 'https://to.somedomain.net/api/0.1/'
+
+		:return: The base URL should match '[\\w\\+\\-\\.]+://[\\w\\+\\-\\.]+(:\\d+)?' e.g. 'https://to.somedomain.net/api/0.1/'
 		:rtype: str
 		"""
 
@@ -311,29 +299,16 @@ class RestApiSession(object):
 
 	def _build_endpoint(self, api_path, params=None, query_params=None):
 		"""
-		Helper function to form API URL.
-		The base URL is '<protocol>://<hostname>[:<port>]/<api base url>'
-		e.g. 'https://to.somedomain.net/api/0.1/'
-		:param api_path: The path to the API end-point that you want to call which does not include
-			the base URL e.g. ``user/login``, ``servers``, etc. This string can contain substitution
-			parameters as denoted by a valid field_name replacement field specification as per
-			:meth:`str.format` e.g. ``cachegroups/{id}`` or ``cachegroups/{id:d}``
+		Helper function to form API URL. The base URL is
+		'<protocol>://<hostname>[:<port>]/<api base url>' e.g. 'https://to.somedomain.net/api/0.1/'
 
+		:param api_path: The path to the API end-point that you want to call which does not include the base URL e.g. ``user/login``, ``servers``, etc. This string can contain substitution parameters as denoted by a valid field_name replacement field specification as per :meth:`str.format` e.g. ``cachegroups/{id}`` or ``cachegroups/{id:d}``
 		:type api_path: str
-		:param params: If :meth:`str.format` field_name replacement field specifications exists in
-			the ``api_path`` use this dictionary to perform replacements of the specifications with
-			the value(s) in the dictionary that match the parameter name(s) e.g. ``{param_id}`` or
-			``{param_id:d}`` in ``api_string`` is replaced by value in ``params['param_id']``.
-
+		:param params: If :meth:`str.format` field_name replacement field specifications exists in the ``api_path`` use this dictionary to perform replacements of the specifications with the value(s) in the dictionary that match the parameter name(s) e.g. ``{param_id}`` or ``{param_id:d}`` in ``api_string`` is replaced by value in ``params['param_id']``.
 		:type params: Union[Dict[str, Any], None]
-		:param query_params: URL query params to provide to the end-point
-			e.g. ``{ 'sort': 'asc', 'maxresults': 200 }`` which translates to something like
-			``?sort=asc&maxresults=200`` which is appended to the request URL
-
+		:param query_params: URL query params to provide to the end-point e.g. ``{ 'sort': 'asc', 'maxresults': 200 }`` which translates to something like ``?sort=asc&maxresults=200`` which is appended to the request URL
 		:type query_params: Union[Dict[str, Any], None]
-		:return: The base url plus the passed and possibly substituted ``api_path`` to form a
-			complete URL to the API resource to request
-
+		:return: The base url plus the passed and possibly substituted ``api_path`` to form a complete URL to the API resource to request
 		:rtype: str
 		:raises: ValueError
 		"""
@@ -382,45 +357,23 @@ class RestApiSession(object):
 		Helper method to perform HTTP operation requests - This is a boilerplate process for HTTP
 		operations.
 
-		:param operation: Name of method to call on the :attr:`self._session` object to perform the
-			HTTP request
-
+		:param operation: Name of method to call on the :attr:`self._session` object to perform the HTTP request
 		:type operation: str
-		:param api_path: The path to the API end-point that you want to call which does not include
-			the URL e.g. ``user/login``, ``servers``, etc. This string can contain substitution
-			parameters as denoted by a valid field_name replacement field specification as per
-			:meth:`str.format` e.g. ``cachegroups/{id}`` or ``cachegroups/{id:d}``
-
+		:param api_path: The path to the API end-point that you want to call which does not include the URL e.g. ``user/login``, ``servers``, etc. This string can contain substitution parameters as denoted by a valid field_name replacement field specification as per :meth:`str.format` e.g. ``cachegroups/{id}`` or ``cachegroups/{id:d}``
 		:type api_path: str
-		:param: query_params: URL query parameters to provide to the endpoint e.g.
-			``{ 'sort': 'asc', 'maxresults': 200 }`` which translates to something like
-			``?sort=asc&maxresults=200`` which is appended to the request URL
-
+		:param: query_params: URL query parameters to provide to the endpoint e.g. ``{ 'sort': 'asc', 'maxresults': 200 }`` which translates to something like ``?sort=asc&maxresults=200`` which is appended to the request URL
 		:type query_params: Union[Dict[str, Any], None]
-		:param: munchify: If :const:`True` encapsulate data to be returned in a :class:`munch.Munch`
-			object which allows keys in a Python dictionary to additionally have attribute access
-			e.g. ``a_dict['a_key']`` with :mod:`munch` becomes ``a_dict['a_key']`` or
-			``a_dict.a_key``
-
+		:param: munchify: If :const:`True` encapsulate data to be returned in a :class:`munch.Munch` object which allows keys in a Python dictionary to additionally have attribute access e.g. ``a_dict['a_key']`` with :mod:`munch` becomes ``a_dict['a_key']`` or ``a_dict.a_key``
 		:type munchify: bool
-		:param kwargs: Passed Keyword Parameters. If you need to send JSON data to the endpoint pass
-			the keyword parameter ``data`` with the Python data structure e.g. a ``dict``. This
-			method will convert it to JSON before sending it to the API endpoint.
-
+		:param kwargs: Passed Keyword Parameters. If you need to send JSON data to the endpoint pass the keyword parameter ``data`` with the Python data structure e.g. a ``dict``. This method will convert it to JSON before sending it to the API endpoint.
 		:type kwargs: Dict[str, Any]
-		:param debug_response: If :const:`True`, the actual response data text will be added to the
-			log if a JSON decoding exception is encountered.
-
+		:param debug_response: If :const:`True`, the actual response data text will be added to the log if a JSON decoding exception is encountered.
 		:type debug_response: bool
 		:type expected_status_codes: Tuple[int]
-		:param: expected_status_codes: expected success HTTP status codes. If the user needs to
-			override the defaults this parameter can be passed e.g. ``(200, 204,)``
-
+		:param: expected_status_codes: expected success HTTP status codes. If the user needs to override the defaults this parameter can be passed e.g. ``(200, 204,)``
 		:type munchify: bool
-
 		:return: Python data structure distilled from JSON from the API request.
-		:rtype: Tuple[Union[Dict[Text, Any], List[Dict[Text, Any]], munch.Munch, List[munch.Munch]],
-					  requests.Response]
+		:rtype: Tuple[Union[Dict[Text, Any], List[Dict[Text, Any]], munch.Munch, List[munch.Munch]], requests.Response]
 		:raises: miscellaneous.exceptions.OperationError
 		"""
 
@@ -486,22 +439,13 @@ class RestApiSession(object):
 	def get(self, api_path, *args, **kwargs):
 		"""
 		Perform http get requests
-		:param api_path: The path to the API end-point that you want to call which does not include
-			the base url e.g. ``user/login``, ``servers``, etc. This string can contain substitution
-			parameters as denoted by a valid field_name replacement field specification as per
-			:meth:`str.format` e.g. ``cachegroups/{id}`` or ``cachegroups/{id:d}``
 
+		:param api_path: The path to the API end-point that you want to call which does not include the base url e.g. ``user/login``, ``servers``, etc. This string can contain substitution parameters as denoted by a valid field_name replacement field specification as per :meth:`str.format` e.g. ``cachegroups/{id}`` or ``cachegroups/{id:d}``
 		:type api_path: str
-		:param kwargs: Passed Keyword Parameters. If you need to send JSON data to the endpoint pass
-			the keyword parameter ``data`` with the Python data structure. This method will convert
-			it to JSON before sending it to the API endpoint. Use ``query_params`` to pass a
-			dictionary of query parameters
-
+		:param kwargs: Passed Keyword Parameters. If you need to send JSON data to the endpoint pass the keyword parameter ``data`` with the Python data structure. This method will convert it to JSON before sending it to the API endpoint. Use ``query_params`` to pass a dictionary of query parameters
 		:type kwargs: Dict[str, Any]
 		:return: Python data structure distilled from JSON from the API request.
-		:rtype: Tuple[Union[Dict[str, Any], List[Dict[str, Any]], munch.Munch, List[munch.Munch]],
-			requests.Response]
-
+		:rtype: Tuple[Union[Dict[str, Any], List[Dict[str, Any]], munch.Munch, List[munch.Munch]], requests.Response]
 		:raises: Union[LoginError, OperationError]
 		"""
 
@@ -510,22 +454,13 @@ class RestApiSession(object):
 	def post(self, api_path, *args, **kwargs):
 		"""
 		Perform http post requests
-		:param api_path: The path to the API end-point that you want to call which does not include
-			the base URL e.g. ``user/login``, ``servers``, etc. This string can contain substitution
-			parameters as denoted by a valid field_name replacement field specification as per
-			:meth:`str.format` e.g. ``cachegroups/{id}`` or ``cachegroups/{id:d}``
 
+		:param api_path: The path to the API end-point that you want to call which does not include the base URL e.g. ``user/login``, ``servers``, etc. This string can contain substitution parameters as denoted by a valid field_name replacement field specification as per :meth:`str.format` e.g. ``cachegroups/{id}`` or ``cachegroups/{id:d}``
 		:type api_path: str
-		:param kwargs: Passed Keyword Parameters. If you need to send JSON data to the endpoint pass
-			the keyword parameter ``data`` with the Python data structure. This method will convert
-			it to JSON before sending it to the API endpoint. Use ``query_params`` to pass a
-			dictionary of query parameters
-
+		:param kwargs: Passed Keyword Parameters. If you need to send JSON data to the endpoint pass the keyword parameter ``data`` with the Python data structure. This method will convert it to JSON before sending it to the API endpoint. Use ``query_params`` to pass a dictionary of query parameters
 		:type kwargs: Dict[str, Any]
 		:return: Python data structure distilled from JSON from the API request.
-		:rtype: Tuple[Union[Dict[str, Any], List[Dict[str, Any]], munch.Munch, List[munch.Munch]],
-			requests.Response]
-
+		:rtype: Tuple[Union[Dict[str, Any], List[Dict[str, Any]], munch.Munch, List[munch.Munch]], requests.Response]
 		:raises: Union[LoginError, OperationError]
 		"""
 
@@ -534,22 +469,13 @@ class RestApiSession(object):
 	def put(self, api_path, *args, **kwargs):
 		"""
 		Perform http put requests
-		:param api_path: The path to the API end-point that you want to call which does not include
-			the base URL e.g. ``user/login``, ``servers``, etc. This string can contain substitution
-			parameters as denoted by a valid field_name replacement field specification as per
-			:meth:`str.format` e.g. ``cachegroups/{id}`` or ``cachegroups/{id:d}``
 
+		:param api_path: The path to the API end-point that you want to call which does not include the base URL e.g. ``user/login``, ``servers``, etc. This string can contain substitution parameters as denoted by a valid field_name replacement field specification as per :meth:`str.format` e.g. ``cachegroups/{id}`` or ``cachegroups/{id:d}``
 		:type api_path: str
-		:param kwargs: Passed Keyword Parameters. If you need to send JSON data to the endpoint pass
-			the keyword parameter ``data`` with the Python data structure. This method will convert
-			it to JSON before sending it to the API endpoint. Use ``query_params`` to pass a
-			dictionary of query parameters
-
+		:param kwargs: Passed Keyword Parameters. If you need to send JSON data to the endpoint pass the keyword parameter ``data`` with the Python data structure. This method will convert it to JSON before sending it to the API endpoint. Use ``query_params`` to pass a dictionary of query parameters
 		:type kwargs: Dict[str, Any]
 		:return: Python data structure distilled from JSON from the API request.
-		:rtype: Tuple[Union[Dict[str, Any], List[Dict[str, Any]], munch.Munch, List[munch.Munch]],
-			requests.Response]
-
+		:rtype: Tuple[Union[Dict[str, Any], List[Dict[str, Any]], munch.Munch, List[munch.Munch]], requests.Response]
 		:raises: Union[LoginError, OperationError]
 		"""
 
@@ -558,22 +484,13 @@ class RestApiSession(object):
 	def delete(self, api_path, *args, **kwargs):
 		"""
 		Perform HTTP DELETE requests
-		:param api_path: The path to the API end-point that you want to call which does not include
-			the base URL e.g. ``user/login``, ``servers``, etc. This string can contain substitution
-			parameters as denoted by a valid field_name replacement field specification as per
-			:meth:`str.format` e.g. ``cachegroups/{id}`` or ``cachegroups/{id:d}``
 
+		:param api_path: The path to the API end-point that you want to call which does not include the base URL e.g. ``user/login``, ``servers``, etc. This string can contain substitution parameters as denoted by a valid field_name replacement field specification as per :meth:`str.format` e.g. ``cachegroups/{id}`` or ``cachegroups/{id:d}``
 		:type api_path: str
-		:param kwargs: Passed Keyword Parameters. If you need to send JSON data to the endpoint pass
-			the keyword parameter ``data`` with the Python data structure. This method will convert
-			it to JSON before sending it to the API endpoint. Use ``query_params`` to pass a
-			dictionary of query parameters
-
+		:param kwargs: Passed Keyword Parameters. If you need to send JSON data to the endpoint pass the keyword parameter ``data`` with the Python data structure. This method will convert it to JSON before sending it to the API endpoint. Use ``query_params`` to pass a dictionary of query parameters
 		:type kwargs: Dict[str, Any]
 		:return: Python data structure distilled from JSON from the API request.
-		:rtype: Tuple[Union[Dict[str, Any], List[Dict[str, Any]], munch.Munch, List[munch.Munch]],
-			requests.Response]
-
+		:rtype: Tuple[Union[Dict[str, Any], List[Dict[str, Any]], munch.Munch, List[munch.Munch]], requests.Response]
 		:raises: Union[LoginError, OperationError]
 		"""
 
diff --git a/traffic_control/clients/python/trafficops/tosession.py b/traffic_control/clients/python/trafficops/tosession.py
index c7db7fe..01f8a1f 100644
--- a/traffic_control/clients/python/trafficops/tosession.py
+++ b/traffic_control/clients/python/trafficops/tosession.py
@@ -66,7 +66,7 @@ class TOSession(RestApiSession):
 			pass
 
 
-	.. caption:: python3
+	.. code-block:: python3
 		:caption: End-point with URL parameters and no query parameters
 
 		@api_request(u'get', u'cdns/{cdn_id:d}', (u'1.1', u'1.2',))
@@ -74,28 +74,28 @@ class TOSession(RestApiSession):
 			pass
 
 
-	.. caption:: python3
+	.. code-block:: python3
 		:caption: End-point with no URL parameters but with query parameters
 
 		@api_request(u'get', u'deliveryservices', (u'1.1', u'1.2',))
 		def get_deliveryservices(self, query_params=None):
 			pass
 
-	.. caption:: python3
+	.. code-block:: python3
 		:caption: End-point with URL parameters and query parameters
 
 		@api_request(u'get', u'deliveryservices/xmlId/{xml_id}/sslkeys', (u'1.1', u'1.2',))
 		def get_deliveryservice_ssl_keys_by_xml_id(self, xml_id=None, query_params=None):
 			pass
 
-	.. caption:: python3
+	.. code-block:: python3
 		:caption: End-point with request data
 
 		@api_request(u'post', u'cdns', (u'1.1', u'1.2',))
 		def create_cdn(self, data=None):
 			pass
 
-	.. caption:: python3
+	.. code-block:: python3
 		:caption: End-point with URL parameters and request data
 
 		@api_request(u'put', u'cdns', (u'1.1', u'1.2',))
@@ -106,17 +106,11 @@ class TOSession(RestApiSession):
 
 	:meth:`get_cdns` calls endpoint :ref:`to-api-cdns` e.g. ``t.get_cdns()``
 
-	:meth:`get_types` calls endpoint :ref:`to-api-types`, optionally with query parameters e.g.
+	:meth:`get_types` calls endpoint :ref:`to-api-types`, optionally with query parameters e.g. ``get_foo_data(id=45, query_params={'sort': 'asc'})`` calls endpoint ``GET api/1.x/foo/45?sort=asc`` (presumably)
 
-	``get_types(query_params={'useInTable': 'servers'})``
-	``get_foo_data(id=45, query_params={'sort': 'asc'})`` calls endpoint
-	``GET api/1.x/foo/45?sort=asc`` (presumably)
+	:meth:`cdns_queue_update` calls endpoint :ref:`to-api-cdns-name-queue_update`, with an ID path parameter and a JSON payload e.g. ``cdns_queue_update(id=1, data={'action': 'queue'})``
 
-	:meth:`cdns_queue_update` calls endpoint :ref:`to-api-cdns-name-queue_update`, with an ID path
-	parameter and a JSON payload e.g. ``cdns_queue_update(id=1, data={'action': 'queue'})``
-
-	.. note:: Only a small subset of the API endpoints are implemented. More can be implemented as
-		needed.
+	.. note:: Only a small subset of the API endpoints are implemented. More can be implemented as needed.
 	"""
 
 	def __init__(self, host_ip, host_port=443, api_version=u'1.3', ssl=True, headers=None,
@@ -191,9 +185,8 @@ class TOSession(RestApiSession):
 	def to_url(self):
 		"""
 		The URL without the api portion. (read-only)
-		:return: The URL should match '[\\w\\+\\-\\.]+://[\\w\\+\\-\\.]+(:\\d+)?' e.g
-			https://to.somedomain.net or https://to.somedomain.net:443
 
+		:return: The URL should match '[\\w\\+\\-\\.]+://[\\w\\+\\-\\.]+(:\\d+)?' e.g https://to.somedomain.net or https://to.somedomain.net:443
 		:rtype: str
 		"""
 
@@ -203,9 +196,8 @@ class TOSession(RestApiSession):
 	def base_url(self):
 		"""
 		Returns the base url. (read-only)
-		:return: The base url should match '[\\w\\+\\-\\.]+://[\\w\\+\\-\\.]+(:\\d+)?' e.g
-			https://to.somedomain.net/api/1.2/
 
+		:return: The base url should match '[\\w\\+\\-\\.]+://[\\w\\+\\-\\.]+(:\\d+)?' e.g https://to.somedomain.net/api/1.2/
 		:rtype: str
 		"""
 
@@ -493,9 +485,8 @@ class TOSession(RestApiSession):
 		"""
 		Retrieve fallback related configurations for a cache group
 		:ref:`to-api-cachegroup_fallbacks`
-		:param query_params: Either cacheGroupId or fallbackId must be used or can be used
-			simultaneously
 
+		:param query_params: Either cacheGroupId or fallbackId must be used or can be used simultaneously
 		:type query_params: Dict[str, int]
 		:rtype: Tuple[Dict[str, Any], requests.Response]
 		:raises: Union[LoginError, OperationError]
@@ -528,9 +519,8 @@ class TOSession(RestApiSession):
 		"""
 		Deletes an existing fallback related configurations for a cache group
 		:ref:`to-api-cachegroup_fallbacks`
-		:param query_params: Either cacheGroupId or fallbackId must be used or can be used
-			simultaneously
 
+		:param query_params: Either cacheGroupId or fallbackId must be used or can be used simultaneously
 		:type query_params: Dict[str, int]
 		:rtype: Tuple[Dict[str, Any], requests.Response]
 		:raises: Union[LoginError, OperationError]
@@ -701,16 +691,18 @@ class TOSession(RestApiSession):
 	#
 	# CDN Topology
 	#
-	@api_request(u'get', u'cdns/{cdn_name:s}/configs', (u'1.2', u'1.3',))
-	def get_cdn_config_info(self, cdn_name=None):
-		"""
-		Retrieves CDN config information
-		:ref:`to-api-cdns-name-configs`
-		:param cdn_name: The CDN name to find configs for
-		:type cdn_name: String
-		:rtype: Tuple[Dict[str, Any], requests.Response]
-		:raises: Union[LoginError, OperationError]
-		"""
+
+	# At the time of this writing (Tues. Jan 15 2019), this endpoint doesn't appear to exist.
+	# @api_request(u'get', u'cdns/{cdn_name:s}/configs', (u'1.2', u'1.3',))
+	# def get_cdn_config_info(self, cdn_name=None):
+	# 	"""
+	# 	Retrieves CDN config information
+	# 	:ref:`to-api-cdns-name-configs`
+	# 	:param cdn_name: The CDN name to find configs for
+	# 	:type cdn_name: String
+	# 	:rtype: Tuple[Dict[str, Any], requests.Response]
+	# 	:raises: Union[LoginError, OperationError]
+	# 	"""
 
 	@api_request(u'get', u'cdns/{cdn_name:s}/configs/monitoring', (u'1.2', u'1.3',))
 	def get_cdn_monitoring_info(self, cdn_name=None):
@@ -741,7 +733,7 @@ class TOSession(RestApiSession):
 	def get_cdn_dns_sec_keys(self, cdn_name=None):
 		"""
 		Gets a list of dnsseckeys for a CDN and all associated Delivery Services
-		:ref:`to-api-cdns-name--name-dnsseckeys`
+		:ref:`to-api-cdns-name-name-dnsseckeys`
 		:param cdn_name: The CDN name to find dnsseckeys info for
 		:type cdn_name: String
 		:rtype: Tuple[Dict[str, Any], requests.Response]
@@ -820,7 +812,7 @@ class TOSession(RestApiSession):
 	#
 	# Config Files and Config File Metadata
 	#
-	@api_request(u'get', u'servers/{host_name:s}/configfiles/ats', (u'1.2', u'1.3',))
+	@api_request(u'get', u'servers/{host_name:s}/configfiles/ats', (u'1.2', u'1.3', u'1.4'))
 	def get_server_config_files(self, host_name=None, query_params=None):
 		"""
 		Get the configuiration files for a given host name
@@ -831,7 +823,7 @@ class TOSession(RestApiSession):
 		:raises: Union[LoginError, OperationError]
 		"""
 
-	@api_request(u'get', u'servers/{host_name:s}/configfiles/ats/{config_file:s}', (u'1.2', u'1.3',))
+	@api_request(u'get', u'servers/{host_name:s}/configfiles/ats/{config_file:s}', (u'1.2', u'1.3', u'1.4'))
 	def get_server_specific_config_file(self, host_name=None, config_file=None, query_params=None):
 		"""
 		Get the configuiration files for a given host name and config file
@@ -909,13 +901,13 @@ class TOSession(RestApiSession):
 		:raises: Union[LoginError, OperationError]
 		"""
 
-	@api_request(u'get', u'deliveryservices/{delivery_service_id:d}/servers/unassigned',
+	@api_request(u'get', u'deliveryservices/{delivery_service_id:d}/unassigned_servers',
 	             (u'1.1', u'1.2', u'1.3',))
 	def get_deliveryservice_unassigned_servers(self, delivery_service_id=None):
 		"""
 		Retrieves properties of CDN EDGE or ORG servers not assigned to a delivery service.
 		(Currently call does not work)
-		:ref:`to-api-deliveryservices-id-servers-unassigned`
+		:ref:`to-api-deliveryservices-id-unassigned_servers`
 		:param delivery_service_id: The delivery service Id
 		:type delivery_service_id: int
 		:rtype: Tuple[Dict[str, Any], requests.Response]
@@ -1081,7 +1073,7 @@ class TOSession(RestApiSession):
 	def delete_deliveryservice_servers_by_id(self, delivery_service_id=None, server_id=None):
 		"""
 		Removes a server (cache) from a delivery service.
-		:ref:`deliveryservice_server-dsid-serverid`
+		:ref:`to-api-deliveryservice_server-dsid-serverid`
 		:param delivery_service_id: The delivery service id
 		:type delivery_service_id: int
 		:param server_id: The server id to remove from delivery service
@@ -1564,7 +1556,7 @@ class TOSession(RestApiSession):
 	def delete_federation_user(self, federation_id=None, user_id=None):
 		"""
 		Delete one or more federation / user assignments.
-		:ref:`to-api-federations-id-users-uid`
+		:ref:`to-api-federations-id-users-id`
 		:param federation_id: Federation ID
 		:type federation_id: int
 		:param user_id: Federation User ID
@@ -2047,7 +2039,7 @@ class TOSession(RestApiSession):
 	#
 	# Server
 	#
-	@api_request(u'get', u'servers', (u'1.1', u'1.2', u'1.3',))
+	@api_request(u'get', u'servers', (u'1.1', u'1.2', u'1.3', u'1.4'))
 	def get_servers(self, query_params=None):
 		"""
 		Get Servers.
@@ -2080,11 +2072,11 @@ class TOSession(RestApiSession):
 		:raises: Union[LoginError, OperationError]
 		"""
 
-	@api_request(u'get', u'servers/total', (u'1.1', u'1.2', u'1.3',))
+	@api_request(u'get', u'servers/totals', (u'1.1', u'1.2', u'1.3',))
 	def get_server_type_count(self):
 		"""
 		Retrieves a count of CDN servers by type
-		:ref:`to-api-servers-total`
+		:ref:`to-api-servers-totals`
 		:rtype: Tuple[Union[Dict[str, Any], List[Dict[str, Any]]], requests.Response]
 		:raises: Union[LoginError, OperationError]
 		"""
@@ -2178,6 +2170,16 @@ class TOSession(RestApiSession):
 		:raises: Union[LoginError, OperationError]
 		"""
 
+	@api_request(u'get', u'servers/{server_name}/update_status', (u'1.1', u'1.2', u'1.3', u'1.4'))
+	def get_server_update_status(self, server_name=None):
+		"""
+		Gets the current update status of a server named ``server_name``.
+		:ref:`to-api-servers-name-update_status`
+		:param server_name: The (short) hostname of the server for which the update status will be fetched
+		:rtype: Tuple[Dict[str, Any], requests.Response]
+		:raises: Union[LoginError, OperationError]
+		"""
+
 	#
 	# Static DNS Entries
 	#
@@ -2239,7 +2241,7 @@ class TOSession(RestApiSession):
 	#
 	# Status
 	#
-	@api_request(u'get', u'statuses', (u'1.1', u'1.2', u'1.3',))
+	@api_request(u'get', u'statuses', (u'1.1', u'1.2', u'1.3', u'1.4'))
 	def get_statuses(self):
 		"""
 		Retrieves a list of the server status codes available.