Ejemplo n.º 1
0
    def wrapper(*args, **kwargs):
        """ Decorator wrapper """
        self = args[0]
        try:
            # Try
            auth = self.request.headers.get('Authorization')
            scheme, _, token = auth.partition(' ')
            if scheme.lower() == 'basic':
                # Decode user and password
                user, _, pwd = base64.decodestring(token).partition(':')
                if user == config.get('api', 'user') and pwd == config.get(
                        'api', 'pass'):
                    return fun(*args, **kwargs)
                else:
                    # Raise UNAUTHORIZED HTTP Error (401)
                    logging.error("Unauthorized access from: %s",
                                  self.request.headers)
                    raise tornado.web.HTTPError(UNAUTHORIZED)

            else:
                # We only support basic authentication
                logging.error("Authentication scheme not recognized")
                return "Authentication scheme not recognized"
        except AttributeError:
            # Raise UNAUTHORIZED HTTP Error (401) if the request is not
            # using autentication (auth will be None and partition() will fail
            logging.error("Unauthorized access from: %s", self.request.headers)
            raise tornado.web.HTTPError(UNAUTHORIZED)
Ejemplo n.º 2
0
    def __delete_instance__(self, inst_id):
        """
        This is a private function that is used by :func:`put` and :func:`delete` methods.
        It deletes all the related information of an instance from the *instance*, *attribute* and *volume* table given the database's *id*
        
        :param inst_id: the id of the instance we want to delete
        :type inst_id: str

        """
        requests.delete(config.get('postgrest', 'attribute_url') + "?instance_id=eq." + str(inst_id))
        requests.delete(config.get('postgrest', 'volume_url') + "?instance_id=eq." + str(inst_id))
        requests.delete(config.get('postgrest', 'instance_url') + "?id=eq." + str(inst_id))
Ejemplo n.º 3
0
    def get(self, **args):
        """
        The *GET* method returns an attribute value from a certain *instance*.
        The parameter <attribute name> is optional. If it's not set the method
        will return all the attributes referred to the instance.  (No any
        special headers for this request)

        :param inst_name: the database name which is given in the url
        :type inst_name: str
        :param attr_name: the attribute name to return
        :type attr_name: str
        :rtype: str/json - the response of the request. A string for a single attribute
          and a json encoded object (a dictionary of attribute_name:value structure.
        :raises: HTTPError - when the requested database name does not exist or
            if in case of an internal error 

        """
        instance_n = args.get('instance')
        attribute_n = args.get('attribute')
        entid = get_instance_id_by_name(instance_n)
        if entid:
            if attribute_n:
                response = requests.get(
                    config.get('postgrest', 'attribute_url') +
                    "?select=value&instance_id=eq." + str(entid) +
                    "&name=eq." + attribute_n)
                if response.ok:
                    data = response.json()
                    if data:
                        self.write(data[0]["value"])
                        self.set_status(OK)
                    else:
                        logging.error("Attribute '" + attribute_n +
                                      "' not found for instance: " +
                                      instance_n)
                        raise tornado.web.HTTPError(NOT_FOUND)
            else:
                filter = json.loads('{"inst_id": ' + str(entid) + '}')
                response = requests.post(config.get('postgrest',
                                                    'get_attributes_url'),
                                         json=filter)
                if response.ok:
                    data = response.json()
                    if data:
                        self.write(data[0]["get_attributes"])
                        self.set_status(OK)
                    else:
                        logging.error("Attributes not found for instance: " +
                                      instance_n)
                        raise tornado.web.HTTPError(NOT_FOUND)
        else:
            logging.error("Instance not found: " + instance_n)
            raise tornado.web.HTTPError(NOT_FOUND)
Ejemplo n.º 4
0
    def get(self, host):
        """ 
        The *GET* method returns the list of ip-aliases registered in a host.
        (No any special headers for this request)

        :param host: the host name which is given in the url
        :type host: str
        :rtype: json -- the response of the request
        :raises: HTTPError - when the requested host or the requested url does not exist

        """

        composed_url = config.get('postgrest',
                                  'host_aliases_url') + '?host=eq.' + host
        logging.info('Requesting ' + composed_url)
        response = requests.get(composed_url)
        data = response.json()
        if response.ok and data:
            logging.debug("response: " + response.text)
            self.write({'response': data})
        elif response.ok:
            logging.warning("Host aliases not found: " + host)
            raise tornado.web.HTTPError(NOT_FOUND)
        else:
            logging.error("Error fetching aliases: " + response.text)
            raise tornado.web.HTTPError(NOT_FOUND)
Ejemplo n.º 5
0
    def delete(self, **args):
        """
        The *DELETE* method deletes an attribute by *instance name* and *attribute name*.
        
        :param instance: the database name which is given in the url
        :type instance: str
        :param attribute: the attribute name which is given in the url
        :type attribute: str
        :raises: HTTPError - when the given database name cannot be found

        """
        instance_n = args.get('instance')
        attribute_n = args.get('attribute')
        
        if not instance_n:
            logging.error("No instance specified")
            raise tornado.web.HTTPError(BAD_REQUEST)
        if not attribute_n:
            logging.error("No attribute specified")
            raise tornado.web.HTTPError(BAD_REQUEST)
        
        entid = get_instance_id_by_name(instance_n)
        if entid:
            response = requests.delete(config.get('postgrest', 'attribute_url') + "?instance_id=eq." + str(entid) + "&name=eq." + attribute_n)
            self.set_status(response.status_code)
        else:
            logging.error("Instance not found: " + instance_n)
            raise tornado.web.HTTPError(NOT_FOUND)
Ejemplo n.º 6
0
    def put(self, **args):
        """
        The *PUT* method updates an attribute into the database wih all the information that is needed.
        The name of the instance and the attribute are set in the URL. The new value of the attribute must be sent in the *request body*.

        :param instance: the database name which is given in the url
        :type instance: str
        :param attribute: the attribute name which is given in the url
        :type attribute: str
        :raises: HTTPError - when the *request body* format is not right or in case of internall error

        """
        logging.debug(self.request.body)
        if not self.request.body:
            logging.error("The request contains no valid data")
            raise tornado.web.HTTPError(BAD_REQUEST)
            
        new_value = self.request.body
        instance_n = args.get('instance')
        attribute_n = args.get('attribute')
        entid = get_instance_id_by_name(instance_n)
        if not entid:
            logging.error("Instance '" + instance_n + "' doest not exist.")
            raise tornado.web.HTTPError(NOT_FOUND)
            
        body = json.loads('{"value":"' + new_value + '"}')
        response = requests.patch(config.get('postgrest', 'attribute_url') + "?instance_id=eq." + str(entid) + "&name=eq." + attribute_n, json=body)
        if response.ok:
            self.set_status(NO_CONTENT)
        else:
            logging.error("Error editing the attribute: " + response.text)
            raise tornado.web.HTTPError(response.status_code)
Ejemplo n.º 7
0
    def get(self, name):
        """Returns the FIM's data for an instance
        (No any special headers for this request)

        :param name: the database name which is given in the url
        :type name: str
        :rtype: json - the response of the request, which will include all the information for the given database

        :raises: HTTPError - whene th given database name does not exist or in case of an internal error

        """

        response = requests.get(config.get('postgrest', 'fim_url') +
                                '?instance_name=eq.' + name,
                                verify=False)
        if response.ok:
            data = response.json()
            if data:
                logging.debug("data: " + json.dumps(data))
                self.write({'data': data})
            else:
                logging.error("Instance not found in FIM: " + name)
                raise tornado.web.HTTPError(NOT_FOUND)
        else:
            logging.error("Error fetching instance information: " +
                          response.text)
            raise tornado.web.HTTPError(response.status_code)
Ejemplo n.º 8
0
    def delete(self, **args):
        """
        The *DELETE* method deletes an attribute by *instance name* and *attribute name*.
        
        :param instance: the database name which is given in the url
        :type instance: str
        :param attribute: the attribute name which is given in the url
        :type attribute: str
        :raises: HTTPError - when the given database name cannot be found

        """
        instance_n = args.get('instance')
        attribute_n = args.get('attribute')

        if not instance_n:
            logging.error("No instance specified")
            raise tornado.web.HTTPError(BAD_REQUEST)
        if not attribute_n:
            logging.error("No attribute specified")
            raise tornado.web.HTTPError(BAD_REQUEST)

        entid = get_instance_id_by_name(instance_n)
        if entid:
            response = requests.delete(
                config.get('postgrest', 'attribute_url') + "?instance_id=eq." +
                str(entid) + "&name=eq." + attribute_n)
            self.set_status(response.status_code)
        else:
            logging.error("Instance not found: " + instance_n)
            raise tornado.web.HTTPError(NOT_FOUND)
Ejemplo n.º 9
0
 def get(self):
     """Returns a valid resources.xml file to import target entities in 
         Rundeck"""
     response = requests.get(config.get('postgrest', 'rundeck_resources_url'))
     if response.ok:
         data = json.loads(response.text)
         d = {}
         for entry in data:
             d[entry[u'db_name']] = entry
         self.set_header('Content-Type', 'text/xml')
         # Page Header
         logging.debug('<?xml version="1.0" encoding="UTF-8"?>')
         self.write('<?xml version="1.0" encoding="UTF-8"?>')
         logging.debug('<project>')
         self.write('<project>')
         for instance in sorted(d.keys()):
             body = d[instance]
             text = ('<node name="%s" description="" hostname="%s" username="******" type="%s" subcategory="%s" port="%s" tags="%s"/>' % 
                     ( instance, # Name
                       body.get(u'hostname'),
                       body.get(u'username'),
                       body.get(u'category'), 
                       body.get(u'db_type'), 
                       body.get(u'port'), 
                       body.get(u'tags')
                       ))
             logging.debug(text)
             self.write(text)
         logging.debug('</project>')
         self.write('</project>')
     else: 
         logging.error("Error fetching Rundeck resources.xml")
         raise tornado.web.HTTPError(NOT_FOUND)
Ejemplo n.º 10
0
    def put(self, **args):
        """
        The *PUT* method updates an attribute into the database wih all the information that is needed.
        The name of the instance and the attribute are set in the URL. The new value of the attribute must be sent in the *request body*.

        :param instance: the database name which is given in the url
        :type instance: str
        :param attribute: the attribute name which is given in the url
        :type attribute: str
        :raises: HTTPError - when the *request body* format is not right or in case of internall error

        """
        logging.debug(self.request.body)
        if not self.request.body:
            logging.error("The request contains no valid data")
            raise tornado.web.HTTPError(BAD_REQUEST)

        new_value = self.request.body
        instance_n = args.get('instance')
        attribute_n = args.get('attribute')
        entid = get_instance_id_by_name(instance_n)
        if not entid:
            logging.error("Instance '" + instance_n + "' doest not exist.")
            raise tornado.web.HTTPError(NOT_FOUND)

        body = json.loads('{"value":"' + new_value + '"}')
        response = requests.patch(config.get('postgrest', 'attribute_url') +
                                  "?instance_id=eq." + str(entid) +
                                  "&name=eq." + attribute_n,
                                  json=body)
        if response.ok:
            self.set_status(NO_CONTENT)
        else:
            logging.error("Error editing the attribute: " + response.text)
            raise tornado.web.HTTPError(response.status_code)
Ejemplo n.º 11
0
    def get(self, host):

        """ 
        The *GET* method returns the list of ip-aliases registered in a host.
        (No any special headers for this request)

        :param host: the host name which is given in the url
        :type host: str
        :rtype: json -- the response of the request
        :raises: HTTPError - when the requested host or the requested url does not exist

        """

        composed_url = config.get('postgrest', 'host_aliases_url') + '?host=eq.' + host
        logging.info('Requesting ' + composed_url )
        response = requests.get(composed_url)
        data = response.json()
        if response.ok and data:
            logging.debug("response: " + response.text)
            self.write({'response' : data})
        elif response.ok:
            logging.warning("Host aliases not found: " + host)
            raise tornado.web.HTTPError(NOT_FOUND)
        else: 
            logging.error("Error fetching aliases: " + response.text)
            raise tornado.web.HTTPError(NOT_FOUND)
Ejemplo n.º 12
0
    def post(self, **args):
        """
        The *POST* method inserts a new attribute into the database for the specified instance.

        In the request body we specify all the information of the *attribute*
        table.
        
        .. note::
            
            * It's possible to insert more than one *attributes* in the same request. 
            * The attribute names are unique for each instance.
            * The creation is not successful 

                * if the client is not authorized or
                * if there is any internal error
                * if the format of the request body is not right or if there is no *database name* field

        :param name: the name of the database to insert the attributes
        :type name: str
        :raises: HTTPError - in case of an internal error
        :request body:  json

        """
        logging.debug(self.request.body)
        if not self.request.body:
            logging.error("The request contains no valid data")
            raise tornado.web.HTTPError(BAD_REQUEST)

        attributes = json.loads(self.request.body)
        instance_n = args.get('instance')
        entid = get_instance_id_by_name(instance_n)
        if attributes:
            if entid:
                insert_attributes = []
                for attribute in attributes:
                    insert_attr = {
                        'instance_id': entid,
                        'name': attribute,
                        'value': attributes[attribute]
                    }
                    logging.debug("Inserting attribute: " +
                                  json.dumps(insert_attr))
                    insert_attributes.append(insert_attr)

                response = requests.post(config.get('postgrest',
                                                    'attribute_url'),
                                         json=insert_attributes)
                if response.ok:
                    self.set_status(CREATED)
                else:
                    logging.error("Error inserting attributes: " +
                                  response.text)
                    raise tornado.web.HTTPError(response.status_code)
            else:
                logging.error("Instance not found: " + instance_n)
                raise tornado.web.HTTPError(NOT_FOUND)
        else:
            logging.error("The request contains no valid data")
            raise tornado.web.HTTPError(BAD_REQUEST)
Ejemplo n.º 13
0
    def __delete_instance__(self, inst_id):
        """
        This is a private function that is used by :func:`put` and :func:`delete` methods.
        It deletes all the related information of an instance from the *instance*, *attribute* and *volume* table given the database's *id*
        
        :param inst_id: the id of the instance we want to delete
        :type inst_id: str

        """
        requests.delete(
            config.get('postgrest', 'attribute_url') + "?instance_id=eq." +
            str(inst_id))
        requests.delete(
            config.get('postgrest', 'volume_url') + "?instance_id=eq." +
            str(inst_id))
        requests.delete(
            config.get('postgrest', 'instance_url') + "?id=eq." + str(inst_id))
Ejemplo n.º 14
0
    def get(self, **args):
        """Returns the metadata of a host or an instance
        The *GET* method returns the instance(s)' metadata given the *host* or the *database name*. 
        (No any special headers for this request)

        :param class: "host" or "instance"
        :type class: str
        :param name: the host or database name which is given in the url
        :type name: str
        :rtype: json - the response of the request 

                * in case of "*host*" it returns all the instances' metadata that are hosted in the specified host
                * in casse of "*instance*" it returns the metadata of just the given database

        :raises: HTTPError - when the <class> argument is not valid ("host" or "instance") or the given host or database name does not exist or in case of an internal error

        """
        name = args.get('name')
        etype = args.get('class')
        if name:
            if etype == u'instance':
                composed_url = config.get(
                    'postgrest', 'metadata_url') + '?db_name=eq.' + name
            elif etype == u'host':
                composed_url = config.get(
                    'postgrest', 'metadata_url') + '?hosts=@>.{' + name + '}'
            else:
                logging.error("Unsupported endpoint")
                raise tornado.web.HTTPError(BAD_REQUEST)
            logging.info('Requesting ' + composed_url)
            response = requests.get(composed_url, verify=False)
            data = response.json()
            if response.ok and data:
                logging.debug("response: " + json.dumps(data))
                self.write({'response': data})
            elif response.ok:
                logging.warning("Instance metadata not found: " + name)
                raise tornado.web.HTTPError(NOT_FOUND)
            else:
                logging.error("Error fetching instance metadata: " +
                              response.text)
                raise tornado.web.HTTPError(response.status_code)
        else:
            logging.error("Unsupported endpoint")
            raise tornado.web.HTTPError(BAD_REQUEST)
Ejemplo n.º 15
0
    def get(self, **args):
        """
        The *GET* method returns an attribute value from a certain *instance*.
        The parameter <attribute name> is optional. If it's not set the method
        will return all the attributes referred to the instance.  (No any
        special headers for this request)

        :param inst_name: the database name which is given in the url
        :type inst_name: str
        :param attr_name: the attribute name to return
        :type attr_name: str
        :rtype: str/json - the response of the request. A string for a single attribute
          and a json encoded object (a dictionary of attribute_name:value structure.
        :raises: HTTPError - when the requested database name does not exist or
            if in case of an internal error 

        """
        instance_n = args.get('instance')
        attribute_n = args.get('attribute')
        entid = get_instance_id_by_name(instance_n)
        if entid:
            if attribute_n:
                response = requests.get(config.get('postgrest', 'attribute_url') + "?select=value&instance_id=eq." + str(entid) + "&name=eq." + attribute_n)
                if response.ok:
                    data = response.json()
                    if data:
                        self.write(data[0]["value"])
                        self.set_status(OK)
                    else:
                        logging.error("Attribute '" + attribute_n + "' not found for instance: " + instance_n)
                        raise tornado.web.HTTPError(NOT_FOUND)
            else:
                filter = json.loads('{"inst_id": ' + str(entid) + '}')
                response = requests.post(config.get('postgrest', 'get_attributes_url'), json=filter)
                if response.ok:
                    data = response.json()
                    if data:
                        self.write(data[0]["get_attributes"])
                        self.set_status(OK)
                    else:
                        logging.error("Attributes not found for instance: " + instance_n)
                        raise tornado.web.HTTPError(NOT_FOUND)
        else:
            logging.error("Instance not found: " + instance_n)
            raise tornado.web.HTTPError(NOT_FOUND)
Ejemplo n.º 16
0
def get_instance_id_by_name(name):
    """Common function to get the ID of an instance by its name."""
    response = requests.get(
        config.get('postgrest', 'instance_url') + "?db_name=eq." + name)
    if response.ok:
        data = response.json()
        if data:
            return data[0]["id"]
    return None
Ejemplo n.º 17
0
    def get(self, **args):
        """Returns the metadata of a host or an instance
        The *GET* method returns the instance(s)' metadata given the *host* or the *database name*. 
        (No any special headers for this request)

        :param class: "host" or "instance"
        :type class: str
        :param name: the host or database name which is given in the url
        :type name: str
        :rtype: json - the response of the request 

                * in case of "*host*" it returns all the instances' metadata that are hosted in the specified host
                * in casse of "*instance*" it returns the metadata of just the given database

        :raises: HTTPError - when the <class> argument is not valid ("host" or "instance") or the given host or database name does not exist or in case of an internal error

        """
        name = args.get('name')
        etype = args.get('class')
        if name:
            if etype == u'instance':
                composed_url = config.get('postgrest', 'metadata_url') + '?db_name=eq.' + name
            elif etype == u'host':
                composed_url = config.get('postgrest', 'metadata_url') + '?hosts=@>.{' + name + '}'
            else:
                logging.error("Unsupported endpoint")
                raise tornado.web.HTTPError(BAD_REQUEST)
            logging.info('Requesting ' + composed_url)
            response = requests.get(composed_url, verify=False)
            data = response.json()
            if response.ok and data:
                logging.debug("response: " + json.dumps(data))
                self.write({'response' : data})
            elif response.ok:
                logging.warning("Instance metadata not found: " + name)
                raise tornado.web.HTTPError(NOT_FOUND)
            else:
                logging.error("Error fetching instance metadata: " + response.text)
                raise tornado.web.HTTPError(response.status_code)
        else:
            logging.error("Unsupported endpoint")
            raise tornado.web.HTTPError(BAD_REQUEST)
Ejemplo n.º 18
0
    def post(self, **args):
        """
        The *POST* method inserts a new attribute into the database for the specified instance.

        In the request body we specify all the information of the *attribute*
        table.
        
        .. note::
            
            * It's possible to insert more than one *attributes* in the same request. 
            * The attribute names are unique for each instance.
            * The creation is not successful 

                * if the client is not authorized or
                * if there is any internal error
                * if the format of the request body is not right or if there is no *database name* field

        :param name: the name of the database to insert the attributes
        :type name: str
        :raises: HTTPError - in case of an internal error
        :request body:  json

        """
        logging.debug(self.request.body)
        if not self.request.body:
            logging.error("The request contains no valid data")
            raise tornado.web.HTTPError(BAD_REQUEST)
            
        attributes = json.loads(self.request.body)
        instance_n = args.get('instance')
        entid = get_instance_id_by_name(instance_n)
        if attributes:
            if entid:
                insert_attributes = []
                for attribute in attributes:
                    insert_attr = {'instance_id': entid, 'name': attribute, 'value': attributes[attribute]}
                    logging.debug("Inserting attribute: " + json.dumps(insert_attr))
                    insert_attributes.append(insert_attr)
                
                response = requests.post(config.get('postgrest', 'attribute_url'), json=insert_attributes)
                if response.ok:
                    self.set_status(CREATED)
                else:
                    logging.error("Error inserting attributes: " + response.text)
                    raise tornado.web.HTTPError(response.status_code)
            else:
                logging.error("Instance not found: " + instance_n)
                raise tornado.web.HTTPError(NOT_FOUND)
        else:
            logging.error("The request contains no valid data")
            raise tornado.web.HTTPError(BAD_REQUEST)
Ejemplo n.º 19
0
    def __init__(self):
        """
        Class constructor. Sets up logging, active handlers and application server
        """
        # Set up log file, level and formatting
        options.log_file_prefix = config.get('logging', 'path')
        options.logging = config.get('logging', 'level')
        options.log_to_stderr = config.getboolean('logging', 'stderr')

        # Port and arguments
        port = config.get('server', 'port')
        define('port', default=port, help='Port to be used')
        parse_command_line([])

        # Override default logging format and date format
        log_format = config.get('logging', 'fmt', raw=True)
        date_format = config.get('logging', 'datefmt', raw=True)
        if date_format:
            formatter = tornado.log.LogFormatter(fmt=log_format,
                                                 datefmt=date_format)
            for logger in logging.getLogger('').handlers:
                logging.info('Overriding log format for %s' % (logger))
                logger.setFormatter(formatter)

        # Defining handlers
        # Removing optional handlers from handler list
        filtered_handlers = self.__handler_filter(handlers, config,
                                                  optionalConfig)
        logging.info("Defining application (url, handler) pairs")
        application = tornado.web.Application(filtered_handlers,
                                              debug=config.getboolean(
                                                  'tornado', 'debug'))

        # Configuring server and SSL
        logging.info("Configuring HTTP server")
        if (config.has_section('ssl')):
            http_server = HTTPServer(application,
                                     ssl_options={
                                         "certfile":
                                         config.get('ssl', 'hostcert'),
                                         "keyfile":
                                         config.get('ssl', 'hostkey'),
                                     })
        else:
            http_server = HTTPServer(application)
            logging.info("Host certificate undefined, SSL is DISABLED")

        # Listening port
        http_server.listen(options.port)

        # Starting
        logging.info("Starting application on port: " + str(port))
        tornado.ioloop.IOLoop.instance().start()
Ejemplo n.º 20
0
    def hostFormat(self, new_ports):
      nodes_url = config.get('smonit', 'nodes_url')
      monitor_hosts = []
      response = requests.get(nodes_url)
      for index in response.get('response').get('items'):
        host = index.get('status').get('addresses')[0].get('address')
        monitor_hosts.append(host)

      monitor_nodes_len = len(monitor_hosts)
      new_ports_len = len(new_ports)
      maxi = max(monitor_hosts_len, new_ports_len)

      return [monitor_hosts[i % monitor_hosts_len] + ':' + new_ports[i % new_ports_len]
              for i in range(maxi)
             ]
Ejemplo n.º 21
0
    def __init__(self):
        """
        Class constructor. Sets up logging, active handlers and application server
        """
        # Set up log file, level and formatting
        options.log_file_prefix = config.get('logging', 'path')
        options.logging = config.get('logging', 'level')
        options.log_to_stderr = config.getboolean('logging', 'stderr')
        
        # Port and arguments
        port = config.get('server', 'port')
        define('port', default=port, help='Port to be used')
        parse_command_line([])
       
        # Override default logging format and date format
        log_format = config.get('logging', 'fmt', raw = True)
        date_format = config.get('logging', 'datefmt', raw = True)
        if date_format:
            formatter = tornado.log.LogFormatter(fmt = log_format, datefmt = date_format)
            for logger in logging.getLogger('').handlers:
                logging.info('Overriding log format for %s' % (logger))
                logger.setFormatter(formatter)

        # Defining handlers
        # Removing optional handlers from handler list 
        filtered_handlers = self.__handler_filter(handlers, config, optionalConfig)
        logging.info("Defining application (url, handler) pairs")
        application = tornado.web.Application(filtered_handlers, 
                            debug = config.getboolean('tornado', 'debug'))
        
        # Configuring server and SSL
        logging.info("Configuring HTTP server")
        if (config.has_section('ssl')):
            http_server = HTTPServer(application,
                    ssl_options = {
                        "certfile" : config.get('ssl', 'hostcert') ,
                        "keyfile" : config.get('ssl', 'hostkey'),
                        })
        else:
            http_server = HTTPServer(application)
            logging.info("Host certificate undefined, SSL is DISABLED")
            
        # Listening port
        http_server.listen(options.port)
        
        # Starting
        logging.info("Starting application on port: " + str(port))
        tornado.ioloop.IOLoop.instance().start()
Ejemplo n.º 22
0
    def __get_instance_id__(self, name):
        """
        This is a private function which is used by :func:`put` and :func:`delete` methods.
        Returns the instance *id* given the database name in order to be able to operate on the instance related tables. It returns *None* if the specified database name does not exist in the *instance* table or in case of internal error.

        :param name: the database name from which we want to get the *id*
        :type name: str
        :rtype: str or None

        """
        response = requests.get(config.get('postgrest', 'instance_url') + "?db_name=eq." + name)
        if response.ok:
            data = response.json()
            if data:
                return data[0]["id"]
            else:
                return None
        else:
            return None
Ejemplo n.º 23
0
    def post(self, **args):
        """
        The *POST* method executes a new Rundeck job and returns the output.
        
        The job and its hash has to be defined in the *api.cfg* configuration file in order to a specific job to be able to be executed.
        
        :param job: the name of the job to be executed which is listed in the configuration file
        :type job: str
        :param node: the name of the node you want the job to be executed
        :type node: str
        :raises: HTTPError - if the job didn't succeed or if the timeout has exceeded or in case of an internal error

        When a job is executed the request call hangs and waits for a response for a maximum time of 10 seconds. The api constantly calls rundeck's api to check if the job has finished. When it finishes it prints out the response or raises an error if it didn't succeed.
        """
        job = args.get('job')
        node = args.get('node')
        response_run = self.__run_job__(job, node)
        if response_run.ok:
            data = json.loads(response_run.text)
            exid = str(data["id"])
            timeout = int(config.get('rundeck', 'timeout')) * 2
            while timeout > 0:
                response_output = self.__get_output__(exid)
                if response_output.ok:
                    output = json.loads(response_output.text)
                    if output["execCompleted"]:
                        if output["execState"] == "succeeded":
                            logging.debug("response: " + response_output.text)
                            self.finish({'response' : output})
                            return
                        else:
                            logging.warning("The job completed with errors: " + exid)
                            raise tornado.web.HTTPError(BAD_GATEWAY)
                    else:
                        timeout -= 1
                        time.sleep(0.500)
                else:
                    logging.error("Error reading the job from Rundeck: " + response_output.text)
                    raise tornado.web.HTTPError(response_output.status_code)
        else:
            logging.error("Error running the job: " + response_run.text)
            raise tornado.web.HTTPError(response_run.status_code)
Ejemplo n.º 24
0
    def __get_instance_id__(self, name):
        """
        This is a private function which is used by :func:`put` and :func:`delete` methods.
        Returns the instance *id* given the database name in order to be able to operate on the instance related tables. It returns *None* if the specified database name does not exist in the *instance* table or in case of internal error.

        :param name: the database name from which we want to get the *id*
        :type name: str
        :rtype: str or None

        """
        response = requests.get(
            config.get('postgrest', 'instance_url') + "?db_name=eq." + name)
        if response.ok:
            data = response.json()
            if data:
                return data[0]["id"]
            else:
                return None
        else:
            return None
Ejemplo n.º 25
0
    def get(self, name):
        """
        The *GET* method returns am *instance* given a *database name*.
        (No any special headers for this request)

        :param name: the database name which is given in the url
        :type name: str
        :rtype: json - the response of the request
        :raises: HTTPError - when the requested database name does not exist or if in case of an internal error 

        """
        response = requests.get(config.get('postgrest', 'instance_url') + "?db_name=eq." + name)
        if response.ok:
            data = response.json()
            if data:
                self.write({'response' : data})
                self.set_status(OK)
            else: 
                logging.error("Instance metadata not found: " + name)
                raise tornado.web.HTTPError(NOT_FOUND)
        else:
            logging.error("Entity metadata not found: " + name)
            raise tornado.web.HTTPError(NOT_FOUND)
Ejemplo n.º 26
0
 def createInstance(self, index, name, app_type, mdi):
   resource_url = config.get('smonit', 'deployment_url')
   payload = {'app_type': app_type,
              'app_name': name + '-' + app_type + str(index),
              'CLUSTER_NAME': name,
              'NODE_MASTER': mdi,
              'NODE_DATA':  mdi,
              'NODE_INGEST': mdi,
              'CLIENT_NODE': name + '-' + 'elasticsearch' + str(index)
             }
   response = requests.post(resource_url,
                            headers={'Authorization': self.authentication},
                            params=payload)
   if response.ok:
     data = response.json()
     self.write(data)
     return data
   elif response.status_code == 409:
     logging.warning("There is already an instance with the same name")
     return {}
   else:
     logging.error("Error in posting in %s with payload %s" %(resource_url, payload))
     raise tornado.web.HTTPError(SERVICE_UNAVAILABLE)
Ejemplo n.º 27
0
    def get(self, name):
        """
        The *GET* method returns am *instance* given a *database name*.
        (No any special headers for this request)

        :param name: the database name which is given in the url
        :type name: str
        :rtype: json - the response of the request
        :raises: HTTPError - when the requested database name does not exist or if in case of an internal error 

        """
        response = requests.get(
            config.get('postgrest', 'instance_url') + "?db_name=eq." + name)
        if response.ok:
            data = response.json()
            if data:
                self.write({'response': data})
                self.set_status(OK)
            else:
                logging.error("Instance metadata not found: " + name)
                raise tornado.web.HTTPError(NOT_FOUND)
        else:
            logging.error("Entity metadata not found: " + name)
            raise tornado.web.HTTPError(NOT_FOUND)
Ejemplo n.º 28
0
    def get(self, name):
        """Returns the FIM's data for an instance
        (No any special headers for this request)

        :param name: the database name which is given in the url
        :type name: str
        :rtype: json - the response of the request, which will include all the information for the given database

        :raises: HTTPError - whene th given database name does not exist or in case of an internal error

        """
        
        response = requests.get(config.get('postgrest', 'fim_url') + '?instance_name=eq.' + name, verify=False)
        if response.ok:
            data = response.json()
            if data:
                logging.debug("data: " + json.dumps(data))
                self.write({'data' : data})
            else:
                logging.error("Instance not found in FIM: " + name)
                raise tornado.web.HTTPError(NOT_FOUND)
        else:
            logging.error("Error fetching instance information: " + response.text)
            raise tornado.web.HTTPError(response.status_code)
Ejemplo n.º 29
0
class Smonit(tornado.web.RequestHandler):
    """
    This is the handler of **/smonit/<name>** endpoint.

    The methods of this class can manage the processing clients which run an Smonnet instance from **sproc** directory.

    The request method implemented for this endpoint is just the :func:`post`.

    """
    authentication = "basic " + \
                      base64.b64encode(config.get('api', 'user') + \
                      ":" + config.get('api', 'pass'))
    #TODO make a get,put and delete method for getting, updating or deleting monitoring clusters
    def post(self, **args):
      """
      Returns the metadata of a host or an instance
      The *GET* method returns the instance(s)' metadata given the *host* or the *database name*.
      (No any special headers for this request)

      :param name: authorized entity to be monitored
      :type name: str
      :param name: the host or database name which is given in the url
      :type name: str
      :rtype: json - the response of the request

              * in case of "*host*" it returns all the instances' metadata that are hosted in the specified host
              * in casse of "*instance*" it returns the metadata of just the given database

      :raises: HTTPError - when the <class> argument is not valid ("host" or "instance") or the given host or database name does not exist or in case of an internal error

      .. note::

        Along with the 'name' should come a default key-value pair with which 'name' is identifiable

      """
      name = args.get('name')
      #TODO  need more checks for any possible existent monitoring instance
      max_params = config.get('smonit','max_param_filters')
      if name:
        monitor_names = config.get('smonit', 'monitor_names')
        if name in monitor_names.split('-'):
          logging.error("The %s is already been registered for monitoring,\
                         if you want update the parameters"
                         %(name))
          self.set_status(CONFLICT)
          raise tornado.web.HTTPError(CONFLICT)
        #TODO configure the number of nodes
        no_nodes = self.get_argument('no_nodes', 3)
        if no_nodes < 2:
          logging.warning("The cluster with %s number of nodes is not possible"
                           %(no_nodes))
          no_nodes = 3

        logging.info("A monitoring cluster with %s nodes is gonna be created"
                      %(no_nodes))
        new_ports = []
        for index in range(no_nodes - 1):
          data = self.createInstance(index, name, 'elasticsearch', 'true')
          logging.info("response: %s" %(data))
          port = self.getPort(data)
          if port:
              new_ports.append(str(port))

        data = self.createInstance(index + 1, name,'elasticsearch', 'false')
        port = self.getPort(data)
        if port:
          new_ports.append(str(port))
        _ = self.createInstance(index + 1, name, 'kibana', None)

        monitor_params = config.get('smonit', 'monitor_params')
        monitor_nodes = config.get('smonit', 'monitor_nodes')
        params = ''

        i = 0
        key = self.get_argument('key' + str(i))
        value = self.get_argument('value' + str(i))
        while (key and value) and i <= max_params:
          logging.info("Entity %s wants to filter in %s with value %s"
                       %(name, key, value))
          #TODO Need to check if the filter for the specific cluster already exists
          if params:
            params = params + ',' + key + ',' + value
          else:
            params = key + ',' + value
          i += 1
          key = self.get_argument('key' + str(i), None)
          value = self.get_argument('value' + str(i), None)

        if not params:
          logging.error("At least a pair of key and value is needed in the params")
          raise tornado.web.HTTPError(BAD_REQUEST)

        #if len(new_ports) != (i-1)
        if not new_ports:
          logging.error("The monitoring cluster was not started successfully")
          raise tornado.web.HTTPError(SERVICE_UNAVAILABLE)

        #new_ports_str = ','.join(new_ports)

        hostPort = self.hostFormat(new_ports)
        hostPort_str = ','.join(hostPort)

        #TODO write the specific host with the port in the config file
        with open(config_file, 'w') as fd:
          config.set('smonit', 'monitor_names',
                      monitor_names + '-' + name)
          config.set('smonit','monitor_params',
                      monitor_params + '-' + params)
          config.set('smonit', 'monitor_nodes',
                     monitor_nodes + '--' + hostPort_str)
          config.set('smonit','new_monitor',
                     'add,' + params + ',' + name + ',' + + hostPort_str)
          config.write(fd)

        logging.info("The '%s' was added for monitoring in %s"
                     %(name, config_file))
        logging.info("The new params are: %s" %(params))
        logging.info("The new monitoring nodes of the cluster are: %s"
                      %(hostPort_str))

        logging.debug("The registered names for monitoring are: %s"
                      %(monitor_names))

        processing_pid = config.get('smonit', 'processing_pid')
        kill(int(processing_pid), signal.SIGHUP)
        print processing_pid

      else:
          logging.error("Unsupported endpoint")
          raise tornado.web.HTTPError(BAD_REQUEST)

    def getPort(self, data):
      nodePort = None
      i = 0
      response = data.get('response', [])
      nodePort = None
      while not nodePort and i < len(response):
        if response[i].get('Service'):
          ports = response[i]['Service']['spec'].get('ports')[0]
          if ports.get('port') == 9200:
            nodePort = ports.get('nodePort')
        i += 1
      return nodePort

    def createInstance(self, index, name, app_type, mdi):
      resource_url = config.get('smonit', 'deployment_url')
      payload = {'app_type': app_type,
                 'app_name': name + '-' + app_type + str(index),
                 'CLUSTER_NAME': name,
                 'NODE_MASTER': mdi,
                 'NODE_DATA':  mdi,
                 'NODE_INGEST': mdi,
                 'CLIENT_NODE': name + '-' + 'elasticsearch' + str(index)
                }
      response = requests.post(resource_url,
                               headers={'Authorization': self.authentication},
                               params=payload)
      if response.ok:
        data = response.json()
        self.write(data)
        return data
      elif response.status_code == 409:
        logging.warning("There is already an instance with the same name")
        return {}
      else:
        logging.error("Error in posting in %s with payload %s" %(resource_url, payload))
        raise tornado.web.HTTPError(SERVICE_UNAVAILABLE)

    def hostFormat(self, new_ports):
      nodes_url = config.get('smonit', 'nodes_url')
      monitor_hosts = []
      response = requests.get(nodes_url)
      for index in response.get('response').get('items'):
        host = index.get('status').get('addresses')[0].get('address')
        monitor_hosts.append(host)

      monitor_nodes_len = len(monitor_hosts)
      new_ports_len = len(new_ports)
      maxi = max(monitor_hosts_len, new_ports_len)

      return [monitor_hosts[i % monitor_hosts_len] + ':' + new_ports[i % new_ports_len]
              for i in range(maxi)
             ]
Ejemplo n.º 30
0
class RundeckTest(AsyncHTTPTestCase, unittest.TestCase):
    """Class for testing Rundeck execution with nosetest"""

    authentication = "basic " + \
                     base64.b64encode(config.get('api', 'user') + \
                     ":" + config.get('api', 'pass'))

    def get_app(self):
        return tornado.web.Application(handlers)

    @patch('dbod.api.rundeck.requests.get')
    def test_get_success(self, mock_get):
        """test when get method is successful"""
        print "test_get_success"
        status_code_test = 200
        response_text = '[{"db_name":"dbod42","hostname":"dbod42.cern.ch","port":"5500","username":"******","db_type":"MYSQL","category":"TEST","tags":"MYSQL,TEST"}, \
        {"db_name":"dbod24","hostname":"dbod24.cern.ch","port":"6603","username":"******","db_type":"PG","category":"PROD","tags":"PG,PROD"}]'

        mock_get.return_value = MagicMock(spec=requests.models.Response,
                                          ok=True,
                                          status_code=status_code_test,
                                          text=response_text)

        response = self.fetch("/api/v1/rundeck/resources.xml")

        self.assertEquals(response.code, 200)

    @patch('dbod.api.rundeck.requests.get')
    def test_get_nosuccess(self, mock_get):
        """test when get method is not successful """
        print "test_get_nosuccess"
        status_code_test_error = 502

        mock_get.return_value = MagicMock(spec=requests.models.Response,
                                          ok=False,
                                          status_code=status_code_test_error)

        response = self.fetch("/api/v1/rundeck/resources.xml")
        self.assertEquals(response.code, 404)

    @timeout(10)
    @patch('dbod.api.rundeck.requests.get')
    @patch('dbod.api.rundeck.requests.post')
    def test_post_job_success(self, mock_post, mock_get):
        """test an execution of a registered job of an existing instance"""
        print "test_post_existing_instance"
        status_code_test = 200
        response_output_running = '{"execCompleted": false, "execState": "running"}'
        response_output_success = '{"execCompleted": true, "execState": "succeeded", "log": "[snapscript_24,snapscript_42]"}'
        response_run = '{"id":42}'

        mock_get.side_effect = [
            MagicMock(spec=requests.models.Response,
                      ok=True,
                      status_code=status_code_test,
                      text=response_output_running),
            MagicMock(spec=requests.models.Response,
                      ok=True,
                      status_code=status_code_test,
                      text=response_output_success)
        ]
        mock_post.return_value = MagicMock(spec=requests.models.Response,
                                           ok=True,
                                           status_code=status_code_test,
                                           text=response_run)

        response = self.fetch("/api/v1/rundeck/job/get-snapshots/instance42",
                              method="POST",
                              headers={'Authorization': self.authentication},
                              body='')
        self.assertEquals(response.code, 200)

    @patch('dbod.api.rundeck.requests.get')
    @patch('dbod.api.rundeck.requests.post')
    def test_post_job_nosuccess(self, mock_post, mock_get):
        """test when the job execution is not successful"""
        print "test_post_job_nosuccess"
        status_code_test = 200
        response_run = '{"id":42}'
        response_output = '{"execCompleted": true, "execState": "failed", "log": "[snapscript_24,snapscript_42]"}'

        mock_get.return_value = MagicMock(spec=requests.models.Response,
                                          ok=True,
                                          status_code=status_code_test,
                                          text=response_output)
        mock_post.return_value = MagicMock(spec=requests.models.Response,
                                           ok=True,
                                           status_code=status_code_test,
                                           text=response_run)

        response = self.fetch("/api/v1/rundeck/job/get-snapshots/instance42",
                              method="POST",
                              headers={'Authorization': self.authentication},
                              body='')
        self.assertEquals(response.code, 502)

    @patch('dbod.api.rundeck.requests.get')
    @patch('dbod.api.rundeck.requests.post')
    def test_post_jobstatus_error(self, mock_post, mock_get):
        """test when the the get request of the job from rundeck status is not successful"""
        print "test_post_jobstatus_error"
        status_code_test = 200
        status_code_test_error = 500
        response_run = '{"id":42}'
        response_output = '{"execCompleted": true, "execState": "succeeded", "log": "[snapscript_24,snapscript_42]"}'

        mock_get.return_value = MagicMock(spec=requests.models.Response,
                                          ok=False,
                                          status_code=status_code_test_error,
                                          text=response_output)
        mock_post.return_value = MagicMock(spec=requests.models.Response,
                                           ok=True,
                                           status_code=status_code_test,
                                           text=response_run)

        response = self.fetch("/api/v1/rundeck/job/get-snapshots/instance42",
                              method="POST",
                              headers={'Authorization': self.authentication},
                              body='')
        self.assertEquals(response.code, status_code_test_error)

    @patch('dbod.api.rundeck.requests.get')
    @patch('dbod.api.rundeck.requests.post')
    def test_post_jobrun_error(self, mock_post, mock_get):
        """test when the the post request of the job to rundeck is not successful"""
        print "test_post_jobrun_error"
        status_code_test = 200
        status_code_test_error = 400
        response_run = '{"id":42}'

        mock_get.return_value = MagicMock(spec=requests.models.Response,
                                          status_code=status_code_test)
        mock_post.return_value = MagicMock(spec=requests.models.Response,
                                           ok=False,
                                           status_code=status_code_test_error,
                                           text=response_run)

        response = self.fetch("/api/v1/rundeck/job/get-snapshots/instance42",
                              method="POST",
                              headers={'Authorization': self.authentication},
                              body='')

        self.assertEquals(response.code, status_code_test_error)
Ejemplo n.º 31
0
    def put(self, name):
        """
        The *PUT* method updates an instance into the database wih all the information that is needed.

        In the request body we specify all the information of the *instance*
        table along with the *attribute* and *volume* tables. 

        The procedure of this method is the following:

        * We extract and separate the information of each table. 
        * We get the *id* of the row from the given (unique) database from the url.
        * If it exists, we delete if any information with that *id* exists in the tables.
        * After that, we insert the information to the related table along with the instance *id*. 
        * In case of more than one attributes we insert each one separetely.  
        * Finally, we update the *instance* table's row (which include the given database name) with the new given information.

        :param name: the database name which is given in the url
        :type name: str
        :raises: HTTPError - when the *request body* format is not right or in case of internall error

        """
        logging.debug(self.request.body)
        instance = json.loads(self.request.body)
        entid = self.__get_instance_id__(name)
        if not entid:
            logging.error("Instance '" + name + "' doest not exist.")
            raise tornado.web.HTTPError(NOT_FOUND)
        
        # Check if the volumes are changed
        if "volumes" in instance:
            volumes = instance["volumes"]
            for volume in volumes:
                volume["instance_id"] = entid
            del instance["volumes"]
            
            # Delete current volumes
            response = requests.delete(config.get('postgrest', 'volume_url') + "?instance_id=eq." + str(entid))
            logging.debug("Volumes to insert: " + json.dumps(volumes))
            if response.ok or response.status_code == 404:
                if len(volumes) > 0:
                    response = requests.post(config.get('postgrest', 'volume_url'), json=volumes)
                    if response.ok:
                        self.set_status(NO_CONTENT)
                    else:
                        logging.error("Error adding volumes: " + response.text)
                        raise tornado.web.HTTPError(response.status_code)
            else:
                logging.error("Error deleting old volumes: " + response.text)
                raise tornado.web.HTTPError(response.status_code)
                
        # Check if the attributes are changed
        if "attributes" in instance:
            attributes = instance["attributes"]
            response = requests.delete(config.get('postgrest', 'attribute_url') + "?instance_id=eq." + str(entid))
            if response.ok or response.status_code == 404:
                if len(attributes) > 0:
                    # Insert the attributes
                    insert_attributes = []
                    for attribute in attributes:
                        insert_attr = {'instance_id': entid, 'name': attribute, 'value': attributes[attribute]}
                        logging.debug("Inserting attribute: " + json.dumps(insert_attr))
                        insert_attributes.append(insert_attr)
                        
                    response = requests.post(config.get('postgrest', 'attribute_url'), json=insert_attributes)
                    if response.ok:
                        self.set_status(NO_CONTENT)
                    else:
                        logging.error("Error inserting attributes: " + response.text)
                        raise tornado.web.HTTPError(response.status_code)
            else:
                logging.error("Error deleting attributes: " + response.text)
                raise tornado.web.HTTPError(response.status_code)
            del instance["attributes"]
        
        if instance:
            # Check if the hosts are changed
            if "hosts" in instance:
                hosts = instance["hosts"][0]
                if len(instance["hosts"]) > 1:
                    for i in range(1, len(instance["hosts"])):
                        hosts = hosts + "," + instance["hosts"][i]
                instance["host"] = hosts
                del instance["hosts"]
        
            response = requests.patch(config.get('postgrest', 'instance_url') + "?db_name=eq." + name, json=instance)
            if response.ok:
                self.set_status(NO_CONTENT)
            else:
                logging.error("Error editing the instance: " + response.text)
                raise tornado.web.HTTPError(response.status_code)
        else:
            self.set_status(NO_CONTENT)
Ejemplo n.º 32
0
    def post(self, name):
        """
        The *POST* method inserts a new instance into the database wih all the
        information that is needed for the creation of it.

        In the request body we specify all the information of the *instance*
        table along with the *attribute* and *volume* tables. We extract and
        separate the information of each table. After inserting the information
        in the *instance* table we use its *id* to relate the specific instance
        with the *attribute* and *volume* table.
        
        .. note::
            
            
            * It's possible to insert more than one *hosts* or *volumes* in one instance.
            * The database names have to be unique
            * If any of the 3 insertions (in *instance*, *attribute*, *volume* table) is not successful then an *Exception* is raised and the private function :func:`__delete_instance__` is used in order to delete what may has been created.  
            * Also, the creation is not successful 

                * if the client is not authorized or
                * if there is any internal error
                * if the format of the request body is not right or if there is no *database name* field

        :param name: the new database name which is given in the url or any other string
        :type name: str
        :raises: HTTPError - in case of an internal error
        :request body:  json

                       - for *instance*: json
                       - for *attribute*: json
                       - for *volume*: list of jsons

        """
        logging.debug(self.request.body)
        instance = json.loads(self.request.body)
        
        attributes = None
        volumes = None
        entid = None
        # Get the attributes
        if "attributes" in instance:
            attributes = instance["attributes"]
            del instance["attributes"]
        
        # Get the hosts
        if "hosts" in instance:
            hosts = instance["hosts"][0]
            if len(instance["hosts"]) > 1:
                for i in range(1, len(instance["hosts"])):
                    hosts = hosts + "," + instance["hosts"][i]
            instance["host"] = hosts
            del instance["hosts"]
        
        # Get the volumes
        if "volumes" in instance:
            volumes = instance["volumes"]
            del instance["volumes"]
        
        # Insert the instance in database using PostREST
        response = requests.post(config.get('postgrest', 'instance_url'), json=instance, headers={'Prefer': 'return=representation'})
        if response.ok:
            entid = json.loads(response.text)["id"]
            logging.info("Created instance " + instance["db_name"])
            logging.debug(response.text)
            self.set_status(CREATED)
        else:
            logging.error("Error creating the instance: " + response.text)
            raise tornado.web.HTTPError(response.status_code)
        
        # Add instance id to volumes
        if volumes:
            for volume in volumes:
                volume["instance_id"] = entid

            # Insert the volumes in database using PostREST
            logging.debug(volumes)
            response = requests.post(config.get('postgrest', 'volume_url'), json=volumes)
            if response.ok:
                logging.debug("Inserting volumes: " + json.dumps(volumes))
                self.set_status(CREATED)
            else:
                logging.error("Error creating the volumes: " + response.text)
                self.__delete_instance__(entid)
                raise tornado.web.HTTPError(response.status_code)
                
        # Insert the attributes
        if attributes:
            insert_attributes = []
            for attribute in attributes:
                insert_attr = {'instance_id': entid, 'name': attribute, 'value': attributes[attribute]}
                logging.debug("Inserting attribute: " + json.dumps(insert_attr))
                insert_attributes.append(insert_attr)
            
            response = requests.post(config.get('postgrest', 'attribute_url'), json=insert_attributes)
            if response.ok:
                self.set_status(CREATED)
            else:
                logging.error("Error inserting attributes: " + response.text)
                self.__delete_instance__(entid)
                raise tornado.web.HTTPError(response.status_code)
Ejemplo n.º 33
0
 def __run_job__(self, job, node):
     """Executes a new Rundeck job and returns the output"""
     jobid = config.get('rundeck-jobs', job)
     if jobid:
         run_job_url = config.get('rundeck', 'api_run_job').format(jobid)
         return requests.post(run_job_url, headers={'Authorization': config.get('rundeck', 'api_authorization')}, verify=False, data = {'filter':'name: ' + node})
Ejemplo n.º 34
0
    def put(self, name):
        """
        The *PUT* method updates an instance into the database wih all the information that is needed.

        In the request body we specify all the information of the *instance*
        table along with the *attribute* and *volume* tables. 

        The procedure of this method is the following:

        * We extract and separate the information of each table. 
        * We get the *id* of the row from the given (unique) database from the url.
        * If it exists, we delete if any information with that *id* exists in the tables.
        * After that, we insert the information to the related table along with the instance *id*. 
        * In case of more than one attributes we insert each one separetely.  
        * Finally, we update the *instance* table's row (which include the given database name) with the new given information.

        :param name: the database name which is given in the url
        :type name: str
        :raises: HTTPError - when the *request body* format is not right or in case of internall error

        """
        logging.debug(self.request.body)
        instance = json.loads(self.request.body)
        entid = self.__get_instance_id__(name)
        if not entid:
            logging.error("Instance '" + name + "' doest not exist.")
            raise tornado.web.HTTPError(NOT_FOUND)

        # Check if the volumes are changed
        if "volumes" in instance:
            volumes = instance["volumes"]
            for volume in volumes:
                volume["instance_id"] = entid
            del instance["volumes"]

            # Delete current volumes
            response = requests.delete(
                config.get('postgrest', 'volume_url') + "?instance_id=eq." +
                str(entid))
            logging.debug("Volumes to insert: " + json.dumps(volumes))
            if response.ok or response.status_code == 404:
                if len(volumes) > 0:
                    response = requests.post(config.get(
                        'postgrest', 'volume_url'),
                                             json=volumes)
                    if response.ok:
                        self.set_status(NO_CONTENT)
                    else:
                        logging.error("Error adding volumes: " + response.text)
                        raise tornado.web.HTTPError(response.status_code)
            else:
                logging.error("Error deleting old volumes: " + response.text)
                raise tornado.web.HTTPError(response.status_code)

        # Check if the attributes are changed
        if "attributes" in instance:
            attributes = instance["attributes"]
            response = requests.delete(
                config.get('postgrest', 'attribute_url') + "?instance_id=eq." +
                str(entid))
            if response.ok or response.status_code == 404:
                if len(attributes) > 0:
                    # Insert the attributes
                    insert_attributes = []
                    for attribute in attributes:
                        insert_attr = {
                            'instance_id': entid,
                            'name': attribute,
                            'value': attributes[attribute]
                        }
                        logging.debug("Inserting attribute: " +
                                      json.dumps(insert_attr))
                        insert_attributes.append(insert_attr)

                    response = requests.post(config.get(
                        'postgrest', 'attribute_url'),
                                             json=insert_attributes)
                    if response.ok:
                        self.set_status(NO_CONTENT)
                    else:
                        logging.error("Error inserting attributes: " +
                                      response.text)
                        raise tornado.web.HTTPError(response.status_code)
            else:
                logging.error("Error deleting attributes: " + response.text)
                raise tornado.web.HTTPError(response.status_code)
            del instance["attributes"]

        if instance:
            # Check if the hosts are changed
            if "hosts" in instance:
                hosts = instance["hosts"][0]
                if len(instance["hosts"]) > 1:
                    for i in range(1, len(instance["hosts"])):
                        hosts = hosts + "," + instance["hosts"][i]
                instance["host"] = hosts
                del instance["hosts"]

            response = requests.patch(config.get('postgrest', 'instance_url') +
                                      "?db_name=eq." + name,
                                      json=instance)
            if response.ok:
                self.set_status(NO_CONTENT)
            else:
                logging.error("Error editing the instance: " + response.text)
                raise tornado.web.HTTPError(response.status_code)
        else:
            self.set_status(NO_CONTENT)
Ejemplo n.º 35
0
    def post(self, **args):
      """
      Returns the metadata of a host or an instance
      The *GET* method returns the instance(s)' metadata given the *host* or the *database name*.
      (No any special headers for this request)

      :param name: authorized entity to be monitored
      :type name: str
      :param name: the host or database name which is given in the url
      :type name: str
      :rtype: json - the response of the request

              * in case of "*host*" it returns all the instances' metadata that are hosted in the specified host
              * in casse of "*instance*" it returns the metadata of just the given database

      :raises: HTTPError - when the <class> argument is not valid ("host" or "instance") or the given host or database name does not exist or in case of an internal error

      .. note::

        Along with the 'name' should come a default key-value pair with which 'name' is identifiable

      """
      name = args.get('name')
      #TODO  need more checks for any possible existent monitoring instance
      max_params = config.get('smonit','max_param_filters')
      if name:
        monitor_names = config.get('smonit', 'monitor_names')
        if name in monitor_names.split('-'):
          logging.error("The %s is already been registered for monitoring,\
                         if you want update the parameters"
                         %(name))
          self.set_status(CONFLICT)
          raise tornado.web.HTTPError(CONFLICT)
        #TODO configure the number of nodes
        no_nodes = self.get_argument('no_nodes', 3)
        if no_nodes < 2:
          logging.warning("The cluster with %s number of nodes is not possible"
                           %(no_nodes))
          no_nodes = 3

        logging.info("A monitoring cluster with %s nodes is gonna be created"
                      %(no_nodes))
        new_ports = []
        for index in range(no_nodes - 1):
          data = self.createInstance(index, name, 'elasticsearch', 'true')
          logging.info("response: %s" %(data))
          port = self.getPort(data)
          if port:
              new_ports.append(str(port))

        data = self.createInstance(index + 1, name,'elasticsearch', 'false')
        port = self.getPort(data)
        if port:
          new_ports.append(str(port))
        _ = self.createInstance(index + 1, name, 'kibana', None)

        monitor_params = config.get('smonit', 'monitor_params')
        monitor_nodes = config.get('smonit', 'monitor_nodes')
        params = ''

        i = 0
        key = self.get_argument('key' + str(i))
        value = self.get_argument('value' + str(i))
        while (key and value) and i <= max_params:
          logging.info("Entity %s wants to filter in %s with value %s"
                       %(name, key, value))
          #TODO Need to check if the filter for the specific cluster already exists
          if params:
            params = params + ',' + key + ',' + value
          else:
            params = key + ',' + value
          i += 1
          key = self.get_argument('key' + str(i), None)
          value = self.get_argument('value' + str(i), None)

        if not params:
          logging.error("At least a pair of key and value is needed in the params")
          raise tornado.web.HTTPError(BAD_REQUEST)

        #if len(new_ports) != (i-1)
        if not new_ports:
          logging.error("The monitoring cluster was not started successfully")
          raise tornado.web.HTTPError(SERVICE_UNAVAILABLE)

        #new_ports_str = ','.join(new_ports)

        hostPort = self.hostFormat(new_ports)
        hostPort_str = ','.join(hostPort)

        #TODO write the specific host with the port in the config file
        with open(config_file, 'w') as fd:
          config.set('smonit', 'monitor_names',
                      monitor_names + '-' + name)
          config.set('smonit','monitor_params',
                      monitor_params + '-' + params)
          config.set('smonit', 'monitor_nodes',
                     monitor_nodes + '--' + hostPort_str)
          config.set('smonit','new_monitor',
                     'add,' + params + ',' + name + ',' + + hostPort_str)
          config.write(fd)

        logging.info("The '%s' was added for monitoring in %s"
                     %(name, config_file))
        logging.info("The new params are: %s" %(params))
        logging.info("The new monitoring nodes of the cluster are: %s"
                      %(hostPort_str))

        logging.debug("The registered names for monitoring are: %s"
                      %(monitor_names))

        processing_pid = config.get('smonit', 'processing_pid')
        kill(int(processing_pid), signal.SIGHUP)
        print processing_pid

      else:
          logging.error("Unsupported endpoint")
          raise tornado.web.HTTPError(BAD_REQUEST)
Ejemplo n.º 36
0
 def __get_output__(self, execution):
     """Returns the output of a job execution"""
     api_job_output = config.get('rundeck', 'api_job_output').format(execution)
     return requests.get(api_job_output, headers={'Authorization': config.get('rundeck', 'api_authorization')}, verify=False)
Ejemplo n.º 37
0
class FunctionalAlias(tornado.web.RequestHandler):

    """
    This is the handler of **/instance/alias/<database name>** endpoint.

    Things that are given for the development of this endpoint:

    * We request indirectly a `Postgres <https://www.postgresql.org/>`_ database through `PostgREST <http://postgrest.com/>`_ which returns a response in JSON format
    * The database's table that is used for this endpoint is called *functional_aliases* and provides information for the functional alias association with an instance.
    * The columns of this table are like that:

    +------------+------------+----------------------+
    |  dns_name  |  db_name   |         alias        |
    +============+============+======================+
    | dbod-dns42 | dbod-db42  | dbod-alias42.cern.ch |
    +------------+------------+----------------------+

        * The *dns_name* is used internally. They point to a list of virtual IP addresses and each bound to a host
        * The *db_name* is the name of the database instance
        * The *alias* is the alias given in order the database to be accessed with that name

    * There is a pool of *dns_names* in this table and the other 2 columns are *NULL* in the begining

    The request methods implemented for this endpoint are:

    * :func:`get`
    * :func:`post`
    * :func:`delete` 

    .. note::

      You need to provide a <*username*> and a <*password*> to use
      :func:`post` and :func:`delete` methods or to provide manually the *Authorization* header.

    """

    url = config.get('postgrest', 'functional_alias_url')

    def get(self, db_name, *args):

        """
        The *GET* method returns the database name's *alias* and *dns name*.
        (No any special headers for this request)

        :param db_name: the database name which is given in the url
        :type db_name: str
        :rtype: json -- the response of the request
        :raises: HTTPError - when the requested database name does not exist or if there is an internal error 
        
        """
        logging.debug('Arguments:' + str(self.request.arguments))
        composed_url = self.url + '?db_name=eq.' + db_name + '&select=dns_name,alias'
        logging.info('Requesting ' + composed_url)
        response = requests.get(composed_url)
        data = response.json()
        if response.ok and data:
            logging.debug("response: " + json.dumps(data))
            self.write({'response' : data})
        elif response.ok:
            logging.warning("Functional alias not found for instance: " + db_name)
            raise tornado.web.HTTPError(NOT_FOUND)
        else:
            logging.error("Error fetching functional alias: " + response.text)
            raise tornado.web.HTTPError(response.status_code)

    @http_basic_auth
    def post(self, db_name, *args):

        """
        The *POST* method inserts a new *database name* and its *alias* into the database. It
        adds the functional alias association for an instance.

        The *dns name* is chosen automatically from a pool; so, in the background this method 
        actually updates the *database name* and *alias* fields, which were *NULL* in the 
        begining.

        .. note::

            This method is not successful:

            * if the *database name* already exists
            * if there are no any *dns names* available
            * if the format of the *request body* is not right
            * if headers have to be specified
            * if the client does not have the right authorization header 
           
        :param db_name: the new database name which is given in the url
        :type db_name: str
        :raises: HTTPError - when the *url* or the *request body* format or the *headers* are not right
        :request body: alias=<alias> - the alias to be inserted for the given *database name* which is given in the *body* of the request

        """

        logging.debug('Arguments:' + str(self.request.arguments))
        try:
            alias = self.get_argument('alias')
            logging.debug("alias: %s" % (alias))
            
            dns_name = self._next_dnsname()

            if dns_name:
                logging.debug("dns_name picked: " + str(dns_name))
                headers = {'Prefer': 'return=representation'}
                insert_data = {"db_name": db_name, 
                               "alias": alias}
                logging.debug("Data to insert: " + str(insert_data))

                composed_url = self.url + '?dns_name=eq.' + dns_name
                logging.debug('Requesting insertion: ' + composed_url)
                
                response = requests.patch(composed_url, json=insert_data, headers=headers)
            
                if response.ok:
                    logging.info('Data inserted in the functional_aliases table')
                    logging.debug(response.text)
                    self.set_status(CREATED)
                else:
                    logging.error("Error inserting the functional alias: " + response.text)
                    self.set_status(response.status_code)
                        
            else:
                logging.error("No dns_name available in the functional_aliases table")
                self.set_status(SERVICE_UNAVAILABLE)
        except:
            logging.error("Argument not recognized or not defined.")
            logging.error("Try adding header 'Content-Type:application/x-www-form-urlencoded'")
            logging.error("The right format should be: alias=<alias>")
            raise tornado.web.HTTPError(BAD_REQUEST)


    @http_basic_auth
    def delete(self, db_name, *args):
        """
        The *DELETE* method deletes or else asssigns to *NULL* the *database name* and 
        *alias* fields. It removes the functional alias association for an instance.

        .. note::
            
            * If the *database name* doesn't exist it doesn't do anything
            * You have to be authorized to use this method
        
        :param db_name: the new database name which is given in the url
        :type db_name: str
        :raises: HTTPError - when the deletion is not successful

        """

        logging.debug('Arguments:' + str(self.request.arguments))

        dns_name = self._get_dns(db_name)
        logging.debug(dns_name)
        if dns_name:
            headers = {'Prefer': 'return=representation', 'Content-Type': 'application/json'}
            composed_url = self.url + '?dns_name=eq.' + dns_name
            logging.debug('Requesting deletion: ' + composed_url)
            delete_data = '{"db_name": null, "alias": null}'
            logging.debug("dns_name to be remained: " + dns_name)
            response = requests.patch(composed_url, json=json.loads(delete_data), headers=headers)

            if response.ok:
                logging.info("Delete success of: " + dns_name)
                logging.debug(response.text)
                self.set_status(NO_CONTENT)
            else:
                logging.error("Unsuccessful deletion")
                raise tornado.web.HTTPError(response.status_code)

        else:
            logging.info("db_name not found. Nothing to do")

    def _next_dnsname(self):
        """
        This is a private function which is used by :func:`post` method.
        Returns the next dnsname which can be used for a newly created instance.  
        If there is no available *dns name* in the pool or if there is any internal error it returns *None*.

        :rtype: str or None

        """
        #LIMIT is not working in postgrest but it uses some headers for that as well
        headers = {'Range-Unit': 'items', 'Range': '0-0'}
        # select the next available dns_name with db_name and alias assigned to NULL
        query_select = '?select=dns_name&order=dns_name.asc&'
        query_filter = 'db_name=is.null&alias=is.null&dns_name=isnot.null'
        composed_url = self.url + query_select + query_filter
        try:
            response_dns = requests.get(composed_url, headers=headers)
            if response_dns.ok:
                response_dns_dict = json.loads(response_dns.text)[0]
                return response_dns_dict['dns_name']
            else:
                return None
        except:
            error_msg = exc_info()[0]
            logging.error(error_msg)
            return None

    def _get_dns(self, db_name):
        """
        This is a private function which is used by :func:`delete` mehtod.
        Returns the *dns name* which is needed in order to set the *db_name* and *alias* to *NULL* (deletion). If the given *database name* which is passed as an argument does not exist then it returns None. 

        :param db_name: the new database name which is given in the url
        :type db_name: str
        :raises: IndexError - when the database name does not exist
        :rtype: str or None

        """
        composed_url = self.url + '?db_name=eq.' + db_name + '&select=dns_name'
        response = requests.get(composed_url)
        if response.ok:
            try:
                dns_name_dict = json.loads(response.text)[0]
                return dns_name_dict['dns_name']
            except IndexError:
                self.set_status(BAD_REQUEST)
                return None
        else:
            self.set_status(SERVICE_UNAVAILABLE) 
            return None
Ejemplo n.º 38
0
class HostTest(AsyncHTTPTestCase, unittest.TestCase):
    """Class for testing Host endpoint with nosetest"""
    
    authentication = "basic " + \
                     base64.b64encode(config.get('api', 'user') + \
                     ":" + config.get('api', 'pass'))

    def get_app(self):
        return tornado.web.Application(handlers)

    @timeout(5)
    @patch('dbod.api.host.requests.get')
    @patch('dbod.api.host.json.dumps')
    @patch('dbod.api.host.Host.write')
    def test_get_valid_name(self, mock_write, mock_json, mock_get):
        """ test a successful get method with a valid given name"""
    	print "test_get_valid_name"
        status_code_test = 200
        response_output = [{u'memory': 512}]
        mock_get.return_value = MagicMock(spec=requests.models.Response,
                                          ok=True,
                                          status_code=status_code_test,
                                          content=response_output)
        #mock_get.json.return_value.content = response_output
        #mock_json.return_value = MagicMock(content=[{"memory": 512}])
        response = self.fetch("/api/v1/host/names/host42")
        self.assertEquals(response.code, status_code_test)
    
    def empty_json(*args,**kwargs):
        class MockResponse:
            
            def __init__(self, ok, status_code, json_data):
                self.status_code_test = status_code
                self.ok = ok
                self.json_data = json_data

            def json(self):
                return self.json_data
        return MockResponse(True,200,[])


    @timeout(5)
    @patch('dbod.api.host.requests.get', side_effect=empty_json)
    def test_get_empty(self, mock_get):
        """test when the response of the request is empty"""
        print "test_get_empty"

        response = self.fetch("/api/v1/host/names/host42")
        self.assertEquals(response.code, 404)
    
    @timeout(5)
    @patch('dbod.api.host.requests.get')
    def test_get_notexist(self, mock_get):
        """test when the given name does not exist"""
        print "test_get_notexist"
        
        status_code_test_error = 502
        response_output = [{u'memory': 512}]
        mock_get.return_value = MagicMock(spec=requests.models.Response,
                                          ok=False,
                                          status_code=status_code_test_error,
                                          content=response_output)
        response = self.fetch("/api/v1/host/names/host42")
        self.assertEquals(response.code, status_code_test_error)
    
    @timeout(5)
    @patch('dbod.api.host.requests.post')
    def test_post_valid(self, mock_post):
        """test when the post request is valid"""
        print "test_post_valid"

        status_code_test = 201
        memory_test = '512'
        body_test = 'memory=' + memory_test
        mock_post.return_value = MagicMock(spec=requests.models.Response,
                                          ok=True,
                                          status_code=status_code_test)

        response = self.fetch("/api/v1/host/names/host42", 
                              method="POST", 
                              headers={'Authorization': self.authentication},
                              body=body_test)  
        self.assertEquals(response.code, status_code_test)
   
    @timeout(5)
    @patch('dbod.api.host.requests.post')
    def test_post_duplicate(self, mock_post):
        """test when the post request tries to insert a duplicate entry"""
        print "test_post_duplicate"

        status_code_test_error = 409
        memory_test = '512'
        body_test = 'memory=' + memory_test
        mock_post.return_value = MagicMock(spec=requests.models.Response,
                                          ok=False,
                                          status_code=status_code_test_error)

        response = self.fetch("/api/v1/host/names/host42", 
                              method="POST", 
                              headers={'Authorization': self.authentication},
                              body=body_test)  
        self.assertEquals(response.code, status_code_test_error)

    @timeout(5)
    @patch('dbod.api.host.requests.post')
    def test_post_wrongargument(self, mock_post):
        """test when the argument in the body of post request is wrong"""
        print "test_post_duplicate"

        status_code_test_error = 400
        memory_test = '512'
        body_test = 'something=' + memory_test
        mock_post.return_value = MagicMock(spec=requests.models.Response,
                                           status_code=status_code_test_error)

        response = self.fetch("/api/v1/host/names/host42", 
                              method="POST", 
                              headers={'Authorization': self.authentication},
                              body=body_test)  
        self.assertEquals(response.code, status_code_test_error)
    
    @timeout(5)
    @patch('dbod.api.host.requests.post')
    def test_post_badargument(self, mock_post):
        """test when the value of the argument of post request is string"""
        print "test_post_badargument"

        status_code_test_error = 400
        memory_test = 'forty-two'
        body_test = 'memory=' + memory_test
        mock_post.return_value = MagicMock(spec=requests.models.Response,
                                           status_code=status_code_test_error)

        response = self.fetch("/api/v1/host/names/host42", 
                              method="POST", 
                              headers={'Authorization': self.authentication},
                              body=body_test)  
        self.assertEquals(response.code, status_code_test_error)

    @timeout(5)
    @patch('dbod.api.host.requests.patch')
    def test_put_valid(self, mock_patch):
        """test when the put request is valid"""
        print "test_put_valid"

        status_code_test = 200
        memory_test = '42'
        body_test = 'memory=' + memory_test
        mock_patch.return_value = MagicMock(spec=requests.models.Response,
                                          ok=True,
                                          status_code=status_code_test)

        response = self.fetch("/api/v1/host/names/host42", 
                              method="PUT", 
                              headers={'Authorization': self.authentication,
                                       'Content-Type': 'application/x-www-form-urlencoded'},
                              body=body_test)  
        self.assertEquals(response.code, status_code_test)

    @timeout(5)
    @patch('dbod.api.host.requests.patch')
    def test_put_notexist(self, mock_patch):
        """test when the name to update with put request does not exist"""
        print "test_put_notexist"

        status_code_test_error = 404
        memory_test = '512'
        body_test = 'memory=' + memory_test
        mock_patch.return_value = MagicMock(spec=requests.models.Response,
                                          ok=False,
                                          status_code=status_code_test_error)

        response = self.fetch("/api/v1/host/names/host42", 
                              method="PUT", 
                              headers={'Authorization': self.authentication,
                                       'Content-Type': 'application/x-www-form-urlencoded'},
                              body=body_test)  
        self.assertEquals(response.code, status_code_test_error)


    @timeout(5)
    @patch('dbod.api.host.requests.patch')
    def test_put_wrongargument(self, mock_patch):
        """test when the argument in the body of put request is wrong"""
        print "test_put_wrongargument"

        status_code_test_error = 400
        memory_test = '42'
        body_test = 'something=' + memory_test
        mock_patch.return_value = MagicMock(spec=requests.models.Response,
                                          status_code=status_code_test_error)

        response = self.fetch("/api/v1/host/names/host42", 
                              method="PUT", 
                              headers={'Authorization': self.authentication,
                                       'Content-Type': 'application/x-www-form-urlencoded'},
                              body=body_test)  
        self.assertEquals(response.code, status_code_test_error)
    
    @timeout(5)
    @patch('dbod.api.host.requests.patch')
    def test_put_badargument(self, mock_patch):
        """test when the value of the argument of put request is string"""
        print "test_put_badargument"

        status_code_test_error = 400
        memory_test = 'forty-two'
        body_test = 'memory=' + memory_test
        mock_patch.return_value = MagicMock(spec=requests.models.Response,
                                          status_code=status_code_test_error)

        response = self.fetch("/api/v1/host/names/host42", 
                              method="PUT", 
                              headers={'Authorization': self.authentication,
                                       'Content-Type': 'application/x-www-form-urlencoded'},
                              body=body_test)  
        self.assertEquals(response.code, status_code_test_error)
    
    @timeout(5)
    @patch('dbod.api.host.requests.delete')
    def test_delete_valid(self, mock_delete):
        """test when the delete request is valid"""
        print "test_delete_valid"

        status_code_test = 200
        memory_test = '512'
        body_test = 'memory=' + memory_test
        mock_delete.return_value = MagicMock(spec=requests.models.Response,
                                             ok=True,
                                             status_code=status_code_test)

        response = self.fetch("/api/v1/host/names/host42", 
                              method="DELETE", 
                              headers={'Authorization': self.authentication})  
        self.assertEquals(response.code, status_code_test)

    @timeout(5)
    @patch('dbod.api.host.requests.delete')
    def test_delete_notexist(self, mock_delete):
        """test when the name to delete with delete request does not exist"""
        print "test_delete_notexist"

        status_code_test_error = 404
        memory_test = '512'
        body_test = 'memory=' + memory_test
        mock_delete.return_value = MagicMock(spec=requests.models.Response,
                                          ok=False,
                                          status_code=status_code_test_error)

        response = self.fetch("/api/v1/host/names/host42", 
                              method="DELETE", 
                              headers={'Authorization': self.authentication})
        self.assertEquals(response.code, status_code_test_error)
Ejemplo n.º 39
0
    def post(self, name):
        """
        The *POST* method inserts a new instance into the database wih all the
        information that is needed for the creation of it.

        In the request body we specify all the information of the *instance*
        table along with the *attribute* and *volume* tables. We extract and
        separate the information of each table. After inserting the information
        in the *instance* table we use its *id* to relate the specific instance
        with the *attribute* and *volume* table.
        
        .. note::
            
            
            * It's possible to insert more than one *hosts* or *volumes* in one instance.
            * The database names have to be unique
            * If any of the 3 insertions (in *instance*, *attribute*, *volume* table) is not successful then an *Exception* is raised and the private function :func:`__delete_instance__` is used in order to delete what may has been created.  
            * Also, the creation is not successful 

                * if the client is not authorized or
                * if there is any internal error
                * if the format of the request body is not right or if there is no *database name* field

        :param name: the new database name which is given in the url or any other string
        :type name: str
        :raises: HTTPError - in case of an internal error
        :request body:  json

                       - for *instance*: json
                       - for *attribute*: json
                       - for *volume*: list of jsons

        """
        logging.debug(self.request.body)
        instance = json.loads(self.request.body)

        attributes = None
        volumes = None
        entid = None
        # Get the attributes
        if "attributes" in instance:
            attributes = instance["attributes"]
            del instance["attributes"]

        # Get the hosts
        if "hosts" in instance:
            hosts = instance["hosts"][0]
            if len(instance["hosts"]) > 1:
                for i in range(1, len(instance["hosts"])):
                    hosts = hosts + "," + instance["hosts"][i]
            instance["host"] = hosts
            del instance["hosts"]

        # Get the volumes
        if "volumes" in instance:
            volumes = instance["volumes"]
            del instance["volumes"]

        # Insert the instance in database using PostREST
        response = requests.post(config.get('postgrest', 'instance_url'),
                                 json=instance,
                                 headers={'Prefer': 'return=representation'})
        if response.ok:
            entid = json.loads(response.text)["id"]
            logging.info("Created instance " + instance["db_name"])
            logging.debug(response.text)
            self.set_status(CREATED)
        else:
            logging.error("Error creating the instance: " + response.text)
            raise tornado.web.HTTPError(response.status_code)

        # Add instance id to volumes
        if volumes:
            for volume in volumes:
                volume["instance_id"] = entid

            # Insert the volumes in database using PostREST
            logging.debug(volumes)
            response = requests.post(config.get('postgrest', 'volume_url'),
                                     json=volumes)
            if response.ok:
                logging.debug("Inserting volumes: " + json.dumps(volumes))
                self.set_status(CREATED)
            else:
                logging.error("Error creating the volumes: " + response.text)
                self.__delete_instance__(entid)
                raise tornado.web.HTTPError(response.status_code)

        # Insert the attributes
        if attributes:
            insert_attributes = []
            for attribute in attributes:
                insert_attr = {
                    'instance_id': entid,
                    'name': attribute,
                    'value': attributes[attribute]
                }
                logging.debug("Inserting attribute: " +
                              json.dumps(insert_attr))
                insert_attributes.append(insert_attr)

            response = requests.post(config.get('postgrest', 'attribute_url'),
                                     json=insert_attributes)
            if response.ok:
                self.set_status(CREATED)
            else:
                logging.error("Error inserting attributes: " + response.text)
                self.__delete_instance__(entid)
                raise tornado.web.HTTPError(response.status_code)
Ejemplo n.º 40
0
class Host(tornado.web.RequestHandler):
   
    """
    This is the handler of **/host/names/<name>** endpoint.

    Things that are given for the development of this endpoint:

    * We request indirectly a `Postgres <https://www.postgresql.org/>`_ database through `PostgREST <http://postgrest.com/>`_ which returns a response in JSON format
    * The database's table that is used for this endpoint is called *host* and provides information for the functional alias association with an instance.
    * The columns of this table are like that:

    +----+-----------+--------+
    | id |  name     | memory |
    +====+===========+========+
    | 42 | dbod-db42 |  1024  |
    +----+-----------+--------+

        * The *name* in this example is the hostname of a node
        * The *memory* is the memory of the node expressed as integer in megabytes

    The request methods implemented for this endpoint are:

    * :func:`get`
    * :func:`post`
    * :func:`put`
    * :func:`delete` 

    .. note::

      You need to provide a <*username*> and a <*password*> to use
      :func:`post`, :func:`put`, and :func:`delete` methods or to 
      provide manually the *Authorization* header.

    """

    url = config.get('postgrest', 'host_url')

    def get(self, name, *args):

        """
        The *GET* method returns the host's memory according to the example given above.
        (No any special headers for this request)

        :param name: the hostname which is given in the url
        :type name: str
        :rtype: json -- the response of the request
        :raises: HTTPError - when the requested name does not exist or if there is an internal 
        error or if the response is empty
        """

        logging.debug('Arguments:' + str(self.request.arguments))
        composed_url = self.url + '?name=eq.' + name + '&select=memory'
        logging.info("Requesting " + composed_url)
        response = requests.get(composed_url)
        data = response.json()
        if response.ok and data:
            logging.debug("response: " + json.dumps(data))
            self.write({'response' : data})
        elif response.ok:
            logging.warning("Name provided is not found in the table of the database: " + name)
            raise tornado.web.HTTPError(NOT_FOUND)
        else:
            logging.error("Error fetching name: " + response.text)
            raise tornado.web.HTTPError(response.status_code)

    @http_basic_auth
    def post(self, name, *args):

        """
        The *POST* method inserts a new *name* and its *memory* size according to the example above. 
        You don't have to specify anything about the *id* since this field should be specified as *serial*.

        .. note::

            This method is not successful:

            * if the *name* already exists
            * if the format of the *request body* is not right
            * if headers have to be specified
            * if the client does not have the right authorization header 
           
        :param name: the new name which is given in the url
        :type name: str
        :raises: HTTPError - when the *url* or the *request body* format or the *headers* are not right orthere is an internal error
        :request body: memory=<memory_int_MB> - the memory (integer in MB) to be inserted for the given *name* which is given in the *body* of the request
        """

        logging.debug('Arguments:' + str(self.request.arguments))
	try:
		memory = int(self.get_argument('memory'))
		logging.debug("memory: %s" %(memory))

                headers = {'Prefer': 'return=representation', 
                           'Content-Type': 'application/json'}
                insert_data = {"name": name,
                               "memory": memory}
                logging.debug("Data to insert: %s" %(insert_data))
                composed_url = self.url + '?name=eq.' + name
                logging.debug('Requesting insertion: ' + composed_url)
                
                response = requests.post(composed_url, 
                                         json=insert_data, 
                                         headers=headers)
                if response.ok:
                        logging.info('Data inserted in the table')
                        logging.debug(response.text)
                        self.set_status(CREATED)
                else:
                        logging.error("Duplicate entry or backend problem: " + response.text)
                        self.set_status(response.status_code)
                        raise tornado.web.HTTPError(response.status_code)

	except tornado.web.MissingArgumentError:
		logging.error("Bad argument given in the POST body request")
		logging.error("Try entering 'memory=<memory_int_MB>'")
		logging.error("Or try adding this header: \
			      'Content-Type: application/x-www-form-urlencoded'")
		self.set_status(BAD_REQUEST)
                raise tornado.web.HTTPError(BAD_REQUEST)
	except ValueError:
		logging.error("The value of the argument in the request body \
			       should be an integer")
		logging.error("Try entering 'memory=<memory_int_MB>'")
		self.set_status(BAD_REQUEST)
                raise tornado.web.HTTPError(BAD_REQUEST)

	
    @http_basic_auth   
    def put(self, name, *args):
        """
        The *PUT* method updates the *memory* size of the given *name* according to the example above.
        You don't have to specify anything about the *id* since this field should be specified as *serial*.


        .. note::
            
            In order to be able to use *PUT* method possibly you have to specify this header:
            'Content-Type: application/x-www-form-urlencoded'.

            This method is not successful:

            * if the *name* does not exist
            * if the format of the *request body* is not right
            * if headers have to be specified
            * if the client does not have the right authorization header 
           
        :param name: the new name which is given in the url
        :type name: str
        :raises: HTTPError - when the *url* or the *request body* format or the *headers* are not right orthere is an internal error
        :request body: memory=<memory_int_MB> - the memory (integer in MB) to be inserted for the given *name* which is given in the *body* of the request
        """

    	logging.debug('Arguments:' + str(self.request.arguments))
	try:
		memory = int(self.get_argument('memory'))
		logging.debug("memory: %s" %(memory))
            
                headers = {'Prefer': 'return=representation', 
                           'Content-Type': 'application/json'}
                update_data = {"name": name,
                               "memory": memory}
                logging.debug("Data to insert: %s" %(update_data))
                composed_url = self.url + '?name=eq.' + name
                logging.debug('Requesting insertion: ' + composed_url)
                response = requests.patch(composed_url, 
                                          json=update_data, 
                                          headers=headers)
                if response.ok:
                        logging.info('Data updated in the table')
                        logging.debug(response.text)
                        self.set_status(OK)
                else:
                        logging.error("Error while updating the new name: " + response.text)
                        self.set_status(response.status_code)
                        raise tornado.web.HTTPError(response.status_code)

	except tornado.web.MissingArgumentError:
		logging.error("Bad argument given in the POST body request")
		logging.error("Try entering 'memory=<memory_int_MB>'")
		logging.error("Or try adding this header: \
                        'Content-Type: application/x-www-form-urlencoded'")
		self.set_status(BAD_REQUEST)
                raise tornado.web.HTTPError(BAD_REQUEST)

	except ValueError:
		logging.error("The value of the argument in the request body \
			       should be an integer")
		logging.error("Try entering 'memory=<memory_int_MB>'")
		self.set_status(BAD_REQUEST)
                raise tornado.web.HTTPError(BAD_REQUEST)

    @http_basic_auth
    def delete(self, name, *args):
        """
        The *DELETE* method deletes an entry from the table given the *name* in the url.
        You don't have to specify anything about the *id*.


        .. note::
            
            This method is not successful:

            * if the *name* does not exist
            * if headers have to be specified
            * if the client does not have the right authorization header 
           
        :param name: the new name which is given in the url
        :type name: str
        :raises: HTTPError - when there is an internal error or the requested name does not exist
        :request body: memory=<memory_int_MB> - the memory (integer in MB) to be inserted for the given *name* which is given in the *body* of the request
        """
	headers = {'Prefer': 'return=representation',
		   'Content-Type': 'application/json'}
	composed_url = self.url + '?name=eq.' + name
	response = requests.delete(composed_url,
				   headers=headers)
	logging.info("Requesting deletion of: " + name)
	if response.ok:
		logging.info("Data deleted")
		logging.debug(response.text)
		self.set_status(OK)
	else:
		logging.error("Error during deletion with code %s: %s" \
			      %(response.status_code, response.text) )
		logging.error("The given name does not exist in the table")
		self.set_status(response.status_code)
                raise tornado.web.HTTPError(response.status_code)
Ejemplo n.º 41
0
class FunctionalAliasTest(AsyncHTTPTestCase, unittest.TestCase):
    """Class for testing functional alias with nosetest"""
    #headers = {'Content-Type': 'application/x-www-form-urlencoded', 
    #           'Prefer': 'return=representation',
    #           'Accept': 'text/json'}
    db_name_test = "dbod42"
    alias_test = "dbod-dbod-42.cern.ch"
    authentication = "basic " + \
                     base64.b64encode(config.get('api', 'user') + \
                     ":" + config.get('api', 'pass'))
    
    def get_app(self):
        return tornado.web.Application(handlers)

    @timeout(5)
    def test_get_single_alias_by_name(self):
        """test for getting the right data"""
        print "test_get_single_alias_by_name"
        db_name = 'dbod01'
        response = self.fetch("/api/v1/instance/alias/%s" %(db_name))
        data = json.loads(response.body)["response"]
        self.assertEquals(response.code, 200)
        #self.assertEquals(json.loads(response.headers)['Content-Type'], 'application/json')
        self.assertEquals(len(data), 1)
        self.assertEquals(data[0]["alias"], "dbod-dbod-01.cern.ch")
        self.assertTrue(data[0]["dns_name"] != None)
        self.assertEquals(response.headers['Content-Type'], 'application/json; charset=UTF-8')

    @timeout(5)
    def test_get_invalid_dbname(self):
        """test when the db_name does not exist"""
        print "test_get_invalid_dbname"
        response = self.fetch("/api/v1/instance/alias/%s" %(self.db_name_test))
        data = response.body
        self.assertEquals(response.code, 404)
        self.assertEquals(len(data), 69)
        self.assertEquals(response.headers['Content-Type'], 'text/html; charset=UTF-8')

    @timeout(5)
    @patch('dbod.api.functionalalias.requests.get')
    def test_get_bad_response(self, mock_get):
        """test when the get response code is not 200. Server/api error"""
        print "test_get_bad_response"
        status_code_test = 503
        mock_get.return_value = MagicMock(spec=requests.models.Response, 
                                          ok=False,
                                          status_code=status_code_test)
        db_name = 'dbod01'
        response = self.fetch("/api/v1/instance/alias/%s" %(db_name))
        self.assertEquals(response.code, status_code_test)
        self.assertEquals(response.headers['Content-Type'], 'text/html; charset=UTF-8')

    @timeout(5)
    def test_novalid_db(self):
        """test when the given db does not exist"""
        response = self.fetch("/api/v1/instance/alias/some_db")
        self.assertEquals(response.code, 404)


    @timeout(5)
    def test_post_valid_request(self):
        """test when the arguments are valid and dns_name is available"""
        print "test_post_valid_request"
        body = 'alias=' + self.alias_test
        response = self.fetch("/api/v1/instance/alias/%s" %(self.db_name_test), 
                              method="POST", 
                              headers={'Authorization': self.authentication},
                              body=body)  
        self.assertEquals(response.code, 201)
        # confirm you get back the right data
        response = self.fetch("/api/v1/instance/alias/%s" %(self.db_name_test))
        data = json.loads(response.body)["response"]
        self.assertEquals(len(data), 1)
        self.assertEquals(data[0]["alias"], self.alias_test)
        self.assertTrue(data[0]["dns_name"] != None)
        # delete what has been created
        self.fetch("/api/v1/instance/alias/%s" %(self.db_name_test), 
                   headers={'Authorization': self.authentication},
                   method="DELETE")

    @timeout(5)
    def test_post_duplicate(self):
        """test when there is a request to insert a db_name which already exists"""
        print "test_post_duplicate"
        body = 'alias=dbod-dbod-01.cern.ch'
        response = self.fetch("/api/v1/instance/alias/dbod01", 
                              method="POST", 
                              headers={'Authorization': self.authentication},
                              body=body)
        self.assertEquals(response.code, 409)    

    @timeout(5)
    def test_post_no_dns(self):
        """test when there are no any dns available"""
        print "test_post_no_dns"
        body = 'alias=' + self.alias_test
        self.fetch("/api/v1/instance/alias/%s" %(self.db_name_test), 
                   method="POST", 
                   headers={'Authorization': self.authentication},
                   body=body)
        body = 'alias=' + 'dbod-dbod-24.cern.ch'
        response = self.fetch("/api/v1/instance/alias/dbod24", 
                              method="POST", 
                              headers={'Authorization': self.authentication},
                              body=body)
        self.assertEquals(response.code, 503)
        # delete what has been created
        self.fetch("/api/v1/instance/alias/%s" %(self.db_name_test), 
                   headers={'Authorization': self.authentication},
                   method="DELETE")


    @timeout(5)
    def test_post_no_valid_argument(self):
        """test if the provided argument is not valid"""
        print "test_post_no_valid_argument"
        body = 'something=%s' + self.alias_test
        response = self.fetch("/api/v1/instance/alias/%s" %(self.db_name_test), 
                              method="POST", 
                              headers={'Authorization': self.authentication},
                              body=body)
        self.assertEquals(response.code, 400)

    @timeout(5)
    def test_post_bad_argument(self):
        """test if the provided value of the argument is not valid"""
        print "test_post_bad_argument"
        body = 'alias ' + self.alias_test
        response = self.fetch("/api/v1/instance/alias/%s" %(self.db_name_test), 
                              method="POST", 
                              headers={'Authorization': self.authentication},
                              body=body)
        self.assertEquals(response.code, 400)
    
    @timeout(5)
    @patch('dbod.api.functionalalias.requests.get')
    def test_post_nextdns_failure(self, mock_get):
        """test when there is a server error when getting an available dns_name"""
        print "test_post_nextdns_failure"
        status_code_test = 503
        mock_get.return_value = MagicMock(spec=requests.models.Response, 
                                          ok=False,
                                          status_code=status_code_test)
        
        body = 'alias=%s' + self.alias_test
        response = self.fetch("/api/v1/instance/alias/%s" %(self.db_name_test), 
                              method="POST", 
                              headers={'Authorization': self.authentication},
                              body=body)  
        # tornado will still raise a Bad Request -- to be improved 
        self.assertEquals(response.code, 503)
        # delete what has maybe been created
        self.fetch("/api/v1/instance/alias/%s" %(self.db_name_test), 
                   headers={'Authorization': self.authentication},
                   method="DELETE")   

    @timeout(5)
    def test_delete_valid_request(self):
        """test when there is a valid request to delete a previous inserted db_name"""
        print "test_delete_valid_request"
        # create entity to be deleted
        body = 'alias=' + self.alias_test
        response = self.fetch("/api/v1/instance/alias/%s" %(self.db_name_test), 
                              method="POST", 
                              headers={'Authorization': self.authentication},
                              body=body)
        response = self.fetch("/api/v1/instance/alias/%s" %(self.db_name_test), 
                              headers={'Authorization': self.authentication},
                              method="DELETE")
        self.assertEquals(response.code, 204)

    @timeout(5)
    def test_delete_invalid_dbname(self):
        """test when the given db_name to be deleted does not exist"""
        print "test_delete_invalid_dbname"
        response = self.fetch("/api/v1/instance/alias/%s" %(self.db_name_test), 
                              headers={'Authorization': self.authentication},
                              method="DELETE")
        self.assertEquals(response.code, 400)

    @timeout(5)
    @patch('dbod.api.functionalalias.requests.get')
    def test_delete_getdns_failure(self, mock_get):
        """test an unsuccessful get of the dns_name"""
        print "test_delete_getdns_failure"
        status_code_test = 503
        mock_get.return_value = MagicMock(spec=requests.models.Response,
                                          ok=False,
                                          status_code=status_code_test)
        response = self.fetch("/api/v1/instance/alias/%s" %('dbod01'),
                              headers={'Authorization': self.authentication},
                              method="DELETE")
        # tornado will still give Bad Request error message -- to be improved
        self.assertEquals(response.code, 503)

    @timeout(5)
    @patch('dbod.api.functionalalias.requests.patch')
    def test_delete_nosuccess(self, mock_patch):
        """test an unsuccessful deletion"""
        print "test_delete_nosuccess_delete"
        status_code_test = 503
        mock_patch.return_value = MagicMock(spec=requests.models.Response,
                                            ok=False,
                                            status_code=status_code_test) 
        response = self.fetch("/api/v1/instance/alias/%s" %('dbod01'),
                              headers={'Authorization': self.authentication},
                              method="DELETE")
        self.assertEquals(response.code, status_code_test)