def generate_template_certificate_authority(self, valid_for, connection=None): """ Attempts to generate an template certificate authority. :param valid_for: The number of days that the template should be valid. :return: The Template object. """ if connection == None: connection = self.connection struct = { 'operation': 'generate-template-certificate-authority', 'valid-for': assert_type(valid_for, int) } uri = connection.uri("certificate-templates", self.template_id(), properties=None) response = connection.post(uri, payload=struct) if response.status_code != 201: raise UnexpectedManagementAPIResponse(response.text) return self
def instance_admin(cls,host,realm,admin,password): """ Initializes the security database of a newly initialized server. :param host: The name or IP address of the host to initialize :param realm: The security realm to install :param admin: The name of the admin user :param password: The password of the admin user """ conn = Connection(host, None) payload = { 'admin-username': admin, 'admin-password': password, 'realm': realm } uri = "{0}://{1}:8001/admin/v1/instance-admin".format( conn.protocol, conn.host) logger = logging.getLogger("marklogic") logger.debug("Initializing security for {0}".format(host)) # N.B. Can't use conn.post here because we don't need auth yet response = requests.post(uri, json=payload, headers={'content-type': 'application/json', 'accept': 'application/json'}) if response.status_code != 202: raise UnexpectedManagementAPIResponse(response.text) # From now on connections require auth... conn = Connection(host, HTTPDigestAuth(admin, password)) data = json.loads(response.text) conn.wait_for_restart(data["restart"]["last-startup"][0]["value"])
def _post_server_config(self, xml, connection): """ Send the server configuration to the a bootstrap host on the cluster to which you wish to couple. This is the first half of the handshake necessary to join a host to a cluster. The results are not intended for introspection. :param connection: The connection credentials to use :param xml: The XML payload from get_server_config() :return: The cluster configuration as a ZIP file. """ uri = "{0}://{1}:8001/admin/v1/cluster-config".format( connection.protocol, connection.host) struct = {'group': 'Default', 'server-config': xml} response = connection.post( uri, payload=struct, content_type='application/x-www-form-urlencoded') if response.status_code != 200: raise UnexpectedManagementAPIResponse(response.text) return response.content
def list(cls, connection, include_names=False): """ List all the certificate authorities. If `include_names` is `True`, then the values in the list will be structured values consisting of the certificate ID and the certificate name separated by a "|". :param connection: The connection to a MarkLogic server :param include_names: Indicates if structured names should be returned. :return: A list of certificate authority IDs. """ uri = connection.uri("certificate-authorities") response = connection.get(uri) if response.status_code != 200: raise UnexpectedManagementAPIResponse(response.text) results = [] json_doc = json.loads(response.text) for item in json_doc['certificate-authorities-default-list'][ 'list-items']['list-item']: if include_names: results.append("{0}|{1}".format(item['idref'], item['nameref'])) else: results.append(item['idref']) return results
def exists(self, connection=None): """ Returns true if (and only if) the specified privilege exists. If the name is a structured value consisting of the kind and the name separated by a "|", as returned by the list() method, then the kind is optional. :param connection: The connection to the MarkLogic database :return: The privilege """ if connection is None: connection = self.connection parameters = [] if self.kind() is not None: parameters = ["kind=" + self.kind()] uri = connection.uri("privileges", self.privilege_name(), parameters=parameters) response = connection.head(uri) if response.status_code == 200: return True elif response.status_code == 404: return False else: raise UnexpectedManagementAPIResponse(response.text)
def instance_init(cls, host): """ Performs first-time initialization of a newly installed server. :param host: The name or IP address of the host to initialize """ conn = Connection(host, None) uri = "{0}://{1}:8001/admin/v1/init".format(conn.protocol, conn.host) logger = logging.getLogger("marklogic") logger.debug("Initializing {0}".format(host)) # This call is a little odd; we special case the 400 error that # occurs if the host has alreadya been initialized. try: response = conn.post( uri, content_type='application/x-www-form-urlencoded') except UnexpectedManagementAPIResponse: response = conn.response if response.status_code == 400: err = json.loads(response.text) if "errorResponse" in err: if "messageCode" in err["errorResponse"]: if err["errorResponse"][ "messageCode"] == "MANAGE-ALREADYINIT": return Host(host) raise if response.status_code != 202: raise UnexpectedManagementAPIResponse(response.text) return Host(host)._set_just_initialized()
def create(self, connection=None): """ Creates the certificate template on the MarkLogic server. :param connection: The connection to a MarkLogic server :return: The Template object """ if connection == None: connection = self.connection uri = connection.uri("certificate-templates") struct = self.marshal() response = connection.post(uri, payload=struct) # All well and good, but we need to know what ID was assigned uri = "{0}://{1}:{2}{3}/properties" \ .format(connection.protocol, connection.host, connection.management_port, response.headers['location']) response = connection.get(uri) if response.status_code == 200: result = Template.unmarshal(json.loads(response.text)) self._config = result._config else: raise UnexpectedManagementAPIResponse(response.text) return self
def list(cls, connection): """ Lists the amps on this cluster. :param connection: A connection to a MarkLogic server :return: A list of amps. """ uri = connection.uri("amps") response = connection.get(uri) # This one isn't like all the others because they're compound # Should return the IDs but the Management API doesn't (yet) allow # access by only the ID. if response.status_code == 200: response_json = json.loads(response.text) amp_count = response_json['amp-default-list']['list-items'][ 'list-count']['value'] result = [] if amp_count > 0: for item in response_json['amp-default-list']['list-items'][ 'list-item']: result.append({ "local-name": item['nameref'], "namespace": item['namespace'], "document-uri": item['document-uri'] }) else: raise UnexpectedManagementAPIResponse(response.text) return result
def create(self, connection=None): """ Create a new amp. :param connection: The server connection :return: The amp object """ if connection is None: connection = self.connection uri = connection.uri("amps") struct = self.marshal() self.logger.debug("Creating {0} amp".format(self.local_name())) response = connection.post(uri, payload=struct) if response.status_code != 201: raise UnexpectedManagementAPIResponse(response.text) if 'etag' in response.headers: self.etag = response.headers['etag'] return self
def list(cls, connection): """ Lists the IDs of tasks available on this cluster. :param connection: A connection to a MarkLogic server :return: A list of task IDs. """ uri = connection.uri("tasks") response = connection.get(uri) if response.status_code == 200: response_json = json.loads(response.text) task_count = response_json['tasks-default-list']['list-items'][ 'list-count']['value'] result = [] if task_count > 0: for item in response_json['tasks-default-list']['list-items'][ 'list-item']: result.append(item['idref']) else: raise UnexpectedManagementAPIResponse(response.text) return result
def create(cls, connection, pem): """ Creates a new certificate authority Note that this is a class method, you cannot create a certificate authority except by uploading a PEM-encoded "certificate authority" certificate. :param connection: The connection to a MarkLogic server :param pem: The PEM-encoded certificate authority certificate :return: The Authority object """ uri = connection.uri("certificate-authorities") response = connection.post(uri, payload=pem, content_type="text/plain") # All well and good, but we need to know what ID was assigned uri = "{0}://{1}:{2}{3}/properties" \ .format(connection.protocol, connection.host, connection.management_port, response.headers['location']) response = connection.get(uri) if response.status_code == 200: result = Authority.unmarshal(json.loads(response.text)) else: raise UnexpectedManagementAPIResponse(response.text) return result
def create(self, group="Default", connection=None): """ Create a new scheduled task. :param connection: The server connection :return: The task object """ if connection is None: connection = self.connection uri = connection.uri("tasks", parameters=["group-id=" + group]) struct = self.marshal() self.logger.debug("Creating {0} task".format(self.type())) response = connection.post(uri, payload=struct) if response.status_code != 201: raise UnexpectedManagementAPIResponse(response.text) if 'etag' in response.headers: self.etag = response.headers['etag'] # All well and good, but we need to know what ID was assigned location = response.headers['location'] qpos = location.index("?") location = location[0:qpos] uri = "{0}://{1}:{2}{3}/properties?group-id={4}" \ .format(connection.protocol, connection.host, connection.management_port, location, group) response = connection.get(uri) if response.status_code == 200: result = Task.unmarshal(json.loads(response.text)) self._config = result._config self.taskid = self._config['task-id'] self.group = group else: raise UnexpectedManagementAPIResponse(response.text) return self
def wait_for_restart(self, last_startup, timestamp_uri="/admin/v1/timestamp"): """Wait for the host to restart. :param last_startup: The last startup time reported in the restart message """ uri = "{0}://{1}:8001{2}".format(self.protocol, self.host, timestamp_uri) done = False count = 24 while not done: try: self.logger.debug("Waiting for restart of {0}" .format(self.host)) response = requests.get(uri, auth=self.auth, headers={'accept': 'application/json'}, verify=self.verify) done = (response.status_code == 200 and response.text != last_startup) except TypeError: self.logger.debug("{0}: {1}".format(response.status_code, response.text)) pass except BadStatusLine: self.logger.debug("{0}: {1}".format(response.status_code, response.text)) pass except ProtocolError: self.logger.debug("{0}: {1}".format(response.status_code, response.text)) pass except ReadTimeoutError: self.logger.debug("ReadTimeoutError error...") pass except ReadTimeout: self.logger.debug("ReadTimeout error...") pass except ConnectionError: self.logger.debug("Connection error...") pass time.sleep(4) # Sleep one more time even after success... count -= 1 if count <= 0: raise UnexpectedManagementAPIResponse("Restart hung?") self.logger.debug("{0} restarted".format(self.host))
def _get_server_config(self): """ Obtain the server configuration. This is the data necessary for the first part of the handshake necessary to join a host to a cluster. The returned data is not intended for introspection. :return: The config. This is always XML. """ connection = Connection(self.host_name(), None) uri = "http://{0}:8001/admin/v1/server-config".format(connection.host) response = connection.get(uri, accept="application/xml") if response.status_code != 200: raise UnexpectedManagementAPIResponse(response.text) return response.text # this is always XML
def list(cls, connection): uri = connection.uri("forests") response = connection.get(uri) if response.status_code == 200: response_json = json.loads(response.text) list_items = response_json['forest-default-list']['list-items'] db_count = list_items['list-count']['value'] result = [] if db_count > 0: for item in list_items['list-item']: result.append(item['nameref']) else: raise UnexpectedManagementAPIResponse(response.text) return result
def generate_temporary_certificate(self, valid_for, common_name, dns_name, ip_addr, connection=None, if_necessary=True): """ Attempts to generate a temporary certificate. If `if_necessary` is true, the server will only generate a new temporary certificate if it does not already have one for the specified server. :param valid_for: The number of days that the template should be valid. :param common_name: The common name for the certificate ("Example Corp") :param dns_name: The DNS name for the cert ("example.com") :param ip_addr: The IP address of the server :param if_necessary: Only generate the cert if it's necessary :return: The Template object. """ if connection == None: connection = self.connection struct = { 'operation': 'generate-temporary-certificate', 'valid-for': assert_type(valid_for, int), 'common-name': common_name, 'dns-name': dns_name, 'ip-addr': ip_addr, 'if-necessary': 'true' if if_necessary else 'false' } uri = connection.uri("certificate-templates", self.template_id(), properties=None) response = connection.post(uri, payload=struct) if response.status_code != 201: raise UnexpectedManagementAPIResponse(response.text) return self
def _response(self): response = self.response if response.status_code < 300: pass elif response.status_code == 404: pass elif response.status_code == 401: raise UnauthorizedAPIRequest(response.text) else: raise UnexpectedManagementAPIResponse(response.text) if response.status_code == 202: data = json.loads(response.text) # restart isn't in data, for example, if you execute a shutdown if "restart" in data: self.wait_for_restart( data["restart"]["last-startup"][0]["value"]) return response
def _post_cluster_config(self, cfgzip, connection): """ Send the cluster configuration to the the server that's joining the cluster. This is the second half of the handshake necessary to join a host to a cluster. :param connection: The connection credentials to use :param cfgzip: The ZIP payload from post_server_config() """ uri = "{0}://{1}:8001/admin/v1/cluster-config" \ .format(connection.protocol, connection.host) response = connection.post(uri, payload=cfgzip, content_type="application/zip") if response.status_code != 202: raise UnexpectedManagementAPIResponse(response.text) data = json.loads(response.text)
def delete(self, connection=None): """ Remove the given amp. :param connection: The server connection :return: The amp object """ if connection is None: connection = self.connection uri = connection.uri("amps", self.local_name(), properties=None, parameters=self._params()) response = connection.delete(uri) if response.status_code != 204: raise UnexpectedManagementAPIResponse(response.text) return self
def list(cls, connection, kind=None, include_actions=False): """ List all the privilege names. Privilege names are structured values, they consist of a kind and a name separated by "|". If `include_actions` is true, the structured values consist of kind, name, and action separated by "|". :param connection: The connection to a MarkLogic server :return: A list of Privilege names. """ uri = connection.uri("privileges") response = connection.get(uri) if response.status_code != 200: raise UnexpectedManagementAPIResponse(response.text) results = [] json_doc = json.loads(response.text) for item in json_doc['privilege-default-list']['list-items'][ 'list-item']: if kind is None: if include_actions: results.append("{0}|{1}|{2}".format( item['kind'], item['nameref'], item['action'])) else: results.append("{0}|{1}".format(item['kind'], item['nameref'])) else: if item['kind'] == kind: if include_actions: results.append("{1}|{2}".format( item['nameref'], item['action'])) else: results.append(item['nameref']) return results
def update(self, connection=None): """ Save the configuration changes with the given connection. :param connection:The server connection :return: The host object """ if connection is None: connection = self.connection uri = connection.uri("tasks", self.taskid, parameters=["group-id=" + self.group]) struct = self.marshal() response = connection.put(uri, payload=struct, etag=self.etag) if response.status_code != 204: raise UnexpectedManagementAPIResponse(response.text) if 'etag' in response.headers: self.etag = response.headers['etag'] return self
def unmarshal(cls, config, connection=None, save_connection=True): result = Forest("temp", connection=connection, save_connection=save_connection) result._config = config result.name = result._config['forest-name'] logger = logging.getLogger("marklogic.forest") atomic = {'availability', 'data-directory', 'database-replication', 'enabled', 'failover-enable', 'fast-data-directory', 'fast-data-max-size', 'forest-name', 'host', 'large-data-directory', 'range', 'rebalancer-enable', 'updates-allowed', 'database' } for key in result._config: olist = [] if key in atomic: pass elif key == 'forest-backup': for backup in result._config['forest-backup']: #logger.debug(backup) temp = None if backup['backup-type'] == 'minutely': temp = ScheduledForestBackup.minutely( backup['backup-directory'], backup['backup-period']) elif backup['backup-type'] == 'hourly': minute = int(backup['backup-start-time'].split(':')[1]) temp = ScheduledForestBackup.hourly( backup['backup-directory'], backup['backup-period'], minute) elif backup['backup-type'] == 'daily': temp = ScheduledForestBackup.daily( backup['backup-directory'], backup['backup-period'], backup['backup-start-time']) elif backup['backup-type'] == 'weekly': temp = ScheduledForestBackup.weekly( backup['backup-directory'], backup['backup-period'], backup['backup-day'], backup['backup-start-time']) elif backup['backup-type'] == 'monthly': temp = ScheduledForestBackup.monthly( backup['backup-directory'], backup['backup-period'], backup['backup-month-day'], backup['backup-start-time']) elif backup['backup-type'] == 'once': temp = ScheduledForestBackup.once( backup['backup-directory'], backup['backup-start-date'], backup['backup-start-time']) else: raise UnexpectedManagementAPIResponse("Unparseable backup") temp._config['backup-id'] = backup['backup-id'] olist.append(temp) result._config['forest-backup'] = olist elif key == 'forest-replica': for rep in result._config['forest-replica']: temp = ForestReplica(rep['replica-name'], rep['host'], rep['data-directory'], rep['large-data-directory'], rep['fast-data-directory']) olist.append(temp) result._config['forest-replica'] = olist else: logger.warning("Unexpected forest property: " + key) return result