672 |
672 |
673 class LogsRequester(object): |
673 class LogsRequester(object): |
674 """Provide facilities to export request logs.""" |
674 """Provide facilities to export request logs.""" |
675 |
675 |
676 def __init__(self, server, config, output_file, |
676 def __init__(self, server, config, output_file, |
677 num_days, append, severity, now): |
677 num_days, append, severity, now, vhost): |
678 """Constructor. |
678 """Constructor. |
679 |
679 |
680 Args: |
680 Args: |
681 server: The RPC server to use. Should be an instance of HttpRpcServer |
681 server: The RPC server to use. Should be an instance of HttpRpcServer |
682 or TestRpcServer. |
682 or TestRpcServer. |
684 output_file: Output file name. |
684 output_file: Output file name. |
685 num_days: Number of days worth of logs to export; 0 for all available. |
685 num_days: Number of days worth of logs to export; 0 for all available. |
686 append: True if appending to an existing file. |
686 append: True if appending to an existing file. |
687 severity: App log severity to request (0-4); None for no app logs. |
687 severity: App log severity to request (0-4); None for no app logs. |
688 now: POSIX timestamp used for calculating valid dates for num_days. |
688 now: POSIX timestamp used for calculating valid dates for num_days. |
|
689 vhost: The virtual host of log messages to get. None for all hosts. |
689 """ |
690 """ |
690 self.server = server |
691 self.server = server |
691 self.config = config |
692 self.config = config |
692 self.output_file = output_file |
693 self.output_file = output_file |
693 self.append = append |
694 self.append = append |
694 self.num_days = num_days |
695 self.num_days = num_days |
695 self.severity = severity |
696 self.severity = severity |
|
697 self.vhost = vhost |
696 self.version_id = self.config.version + ".1" |
698 self.version_id = self.config.version + ".1" |
697 self.sentinel = None |
699 self.sentinel = None |
698 self.write_mode = "w" |
700 self.write_mode = "w" |
699 if self.append: |
701 if self.append: |
700 self.sentinel = FindSentinel(self.output_file) |
702 self.sentinel = FindSentinel(self.output_file) |
768 } |
770 } |
769 if offset: |
771 if offset: |
770 kwds["offset"] = offset |
772 kwds["offset"] = offset |
771 if self.severity is not None: |
773 if self.severity is not None: |
772 kwds["severity"] = str(self.severity) |
774 kwds["severity"] = str(self.severity) |
|
775 if self.vhost is not None: |
|
776 kwds["vhost"] = str(self.vhost) |
773 response = self.server.Send("/api/request_logs", payload=None, **kwds) |
777 response = self.server.Send("/api/request_logs", payload=None, **kwds) |
774 response = response.replace("\r", "\0") |
778 response = response.replace("\r", "\0") |
775 lines = response.splitlines() |
779 lines = response.splitlines() |
776 logging.info("Received %d bytes, %d records.", len(response), len(lines)) |
780 logging.info("Received %d bytes, %d records.", len(response), len(lines)) |
777 offset = None |
781 offset = None |
1787 rpc_server = self._GetRpcServer() |
1791 rpc_server = self._GetRpcServer() |
1788 logs_requester = LogsRequester(rpc_server, appyaml, self.args[1], |
1792 logs_requester = LogsRequester(rpc_server, appyaml, self.args[1], |
1789 self.options.num_days, |
1793 self.options.num_days, |
1790 self.options.append, |
1794 self.options.append, |
1791 self.options.severity, |
1795 self.options.severity, |
1792 time.time()) |
1796 time.time(), |
|
1797 self.options.vhost) |
1793 logs_requester.DownloadLogs() |
1798 logs_requester.DownloadLogs() |
1794 |
1799 |
1795 def _RequestLogsOptions(self, parser): |
1800 def _RequestLogsOptions(self, parser): |
1796 """Adds request_logs-specific options to 'parser'. |
1801 """Adds request_logs-specific options to 'parser'. |
1797 |
1802 |
1811 parser.add_option("--severity", type="int", dest="severity", |
1816 parser.add_option("--severity", type="int", dest="severity", |
1812 action="store", default=None, |
1817 action="store", default=None, |
1813 help="Severity of app-level log messages to get. " |
1818 help="Severity of app-level log messages to get. " |
1814 "The range is 0 (DEBUG) through 4 (CRITICAL). " |
1819 "The range is 0 (DEBUG) through 4 (CRITICAL). " |
1815 "If omitted, only request logs are returned.") |
1820 "If omitted, only request logs are returned.") |
|
1821 parser.add_option("--vhost", type="string", dest="vhost", |
|
1822 action="store", default=None, |
|
1823 help="The virtual host of log messages to get. " |
|
1824 "If omitted, all log messages are returned.") |
1816 |
1825 |
1817 def CronInfo(self, now=None, output=sys.stdout): |
1826 def CronInfo(self, now=None, output=sys.stdout): |
1818 """Displays information about cron definitions. |
1827 """Displays information about cron definitions. |
1819 |
1828 |
1820 Args: |
1829 Args: |
1832 for entry in cron_entries.cron: |
1841 for entry in cron_entries.cron: |
1833 description = entry.description |
1842 description = entry.description |
1834 if not description: |
1843 if not description: |
1835 description = "<no description>" |
1844 description = "<no description>" |
1836 print >>output, "\n%s:\nURL: %s\nSchedule: %s" % (description, |
1845 print >>output, "\n%s:\nURL: %s\nSchedule: %s" % (description, |
1837 entry.schedule, |
1846 entry.url, |
1838 entry.url) |
1847 entry.schedule) |
1839 schedule = groctimespecification.GrocTimeSpecification(entry.schedule) |
1848 schedule = groctimespecification.GrocTimeSpecification(entry.schedule) |
1840 matches = schedule.GetMatches(now, self.options.num_runs) |
1849 matches = schedule.GetMatches(now, self.options.num_runs) |
1841 for match in matches: |
1850 for match in matches: |
1842 print >>output, "%s, %s from now" % ( |
1851 print >>output, "%s, %s from now" % ( |
1843 match.strftime("%Y-%m-%d %H:%M:%S"), match - now) |
1852 match.strftime("%Y-%m-%d %H:%M:%S"), match - now) |
1851 parser.add_option("-n", "--num_runs", type="int", dest="num_runs", |
1860 parser.add_option("-n", "--num_runs", type="int", dest="num_runs", |
1852 action="store", default=5, |
1861 action="store", default=5, |
1853 help="Number of runs of each cron job to display" |
1862 help="Number of runs of each cron job to display" |
1854 "Default is 5") |
1863 "Default is 5") |
1855 |
1864 |
1856 def _CheckRequiredUploadOptions(self): |
1865 def _CheckRequiredLoadOptions(self): |
1857 """Checks that upload options are present.""" |
1866 """Checks that upload/download options are present.""" |
1858 for option in ["filename", "kind", "config_file"]: |
1867 for option in ["filename", "kind", "config_file"]: |
1859 if getattr(self.options, option) is None: |
1868 if getattr(self.options, option) is None: |
1860 self.parser.error("Option '%s' is required." % option) |
1869 self.parser.error("Option '%s' is required." % option) |
1861 if not self.options.url: |
1870 if not self.options.url: |
1862 self.parser.error("You must have google.appengine.ext.remote_api.handler " |
1871 self.parser.error("You must have google.appengine.ext.remote_api.handler " |
1863 "assigned to an endpoint in app.yaml, or provide " |
1872 "assigned to an endpoint in app.yaml, or provide " |
1864 "the url of the handler via the 'url' option.") |
1873 "the url of the handler via the 'url' option.") |
1865 |
1874 |
1866 def InferUploadUrl(self, appyaml): |
1875 def InferRemoteApiUrl(self, appyaml): |
1867 """Uses app.yaml to determine the remote_api endpoint. |
1876 """Uses app.yaml to determine the remote_api endpoint. |
1868 |
1877 |
1869 Args: |
1878 Args: |
1870 appyaml: A parsed app.yaml file. |
1879 appyaml: A parsed app.yaml file. |
1871 |
1880 |
1883 return "http://%s.appspot.com%s" % (app_id, handler.url) |
1892 return "http://%s.appspot.com%s" % (app_id, handler.url) |
1884 else: |
1893 else: |
1885 return "http://%s%s" % (server, handler.url) |
1894 return "http://%s%s" % (server, handler.url) |
1886 return None |
1895 return None |
1887 |
1896 |
1888 def RunBulkloader(self, **kwargs): |
1897 def RunBulkloader(self, arg_dict): |
1889 """Invokes the bulkloader with the given keyword arguments. |
1898 """Invokes the bulkloader with the given keyword arguments. |
1890 |
1899 |
1891 Args: |
1900 Args: |
1892 kwargs: Keyword arguments to pass to bulkloader.Run(). |
1901 arg_dict: Dictionary of arguments to pass to bulkloader.Run(). |
1893 """ |
1902 """ |
1894 try: |
1903 try: |
1895 import sqlite3 |
1904 import sqlite3 |
1896 except ImportError: |
1905 except ImportError: |
1897 logging.error("upload_data action requires SQLite3 and the python " |
1906 logging.error("upload_data action requires SQLite3 and the python " |
1898 "sqlite3 module (included in python since 2.5).") |
1907 "sqlite3 module (included in python since 2.5).") |
1899 sys.exit(1) |
1908 sys.exit(1) |
1900 |
1909 |
1901 sys.exit(bulkloader.Run(kwargs)) |
1910 sys.exit(bulkloader.Run(arg_dict)) |
1902 |
1911 |
1903 def PerformUpload(self, run_fn=None): |
1912 def _SetupLoad(self): |
1904 """Performs a datastore upload via the bulkloader. |
1913 """Performs common verification and set up for upload and download.""" |
1905 |
|
1906 Args: |
|
1907 run_fn: Function to invoke the bulkloader, used for testing. |
|
1908 """ |
|
1909 if run_fn is None: |
|
1910 run_fn = self.RunBulkloader |
|
1911 |
|
1912 if len(self.args) != 1: |
1914 if len(self.args) != 1: |
1913 self.parser.error("Expected <directory> argument.") |
1915 self.parser.error("Expected <directory> argument.") |
1914 |
1916 |
1915 basepath = self.args[0] |
1917 basepath = self.args[0] |
1916 appyaml = self._ParseAppYaml(basepath) |
1918 appyaml = self._ParseAppYaml(basepath) |
1917 |
1919 |
1918 self.options.app_id = appyaml.application |
1920 self.options.app_id = appyaml.application |
1919 |
1921 |
1920 if not self.options.url: |
1922 if not self.options.url: |
1921 url = self.InferUploadUrl(appyaml) |
1923 url = self.InferRemoteApiUrl(appyaml) |
1922 if url is not None: |
1924 if url is not None: |
1923 self.options.url = url |
1925 self.options.url = url |
1924 |
1926 |
1925 self._CheckRequiredUploadOptions() |
1927 self._CheckRequiredLoadOptions() |
1926 |
1928 |
1927 if self.options.batch_size < 1: |
1929 if self.options.batch_size < 1: |
1928 self.parser.error("batch_size must be 1 or larger.") |
1930 self.parser.error("batch_size must be 1 or larger.") |
1929 |
1931 |
1930 if verbosity == 1: |
1932 if verbosity == 1: |
1932 self.options.debug = False |
1934 self.options.debug = False |
1933 else: |
1935 else: |
1934 logging.getLogger().setLevel(logging.DEBUG) |
1936 logging.getLogger().setLevel(logging.DEBUG) |
1935 self.options.debug = True |
1937 self.options.debug = True |
1936 |
1938 |
|
1939 def _MakeLoaderArgs(self): |
|
1940 return dict([(arg_name, getattr(self.options, arg_name, None)) for |
|
1941 arg_name in ( |
|
1942 "app_id", |
|
1943 "url", |
|
1944 "filename", |
|
1945 "batch_size", |
|
1946 "kind", |
|
1947 "num_threads", |
|
1948 "bandwidth_limit", |
|
1949 "rps_limit", |
|
1950 "http_limit", |
|
1951 "db_filename", |
|
1952 "config_file", |
|
1953 "auth_domain", |
|
1954 "has_header", |
|
1955 "loader_opts", |
|
1956 "log_file", |
|
1957 "passin", |
|
1958 "email", |
|
1959 "debug", |
|
1960 "exporter_opts", |
|
1961 "result_db_filename", |
|
1962 )]) |
|
1963 |
|
1964 def PerformDownload(self, run_fn=None): |
|
1965 """Performs a datastore download via the bulkloader. |
|
1966 |
|
1967 Args: |
|
1968 run_fn: Function to invoke the bulkloader, used for testing. |
|
1969 """ |
|
1970 if run_fn is None: |
|
1971 run_fn = self.RunBulkloader |
|
1972 self._SetupLoad() |
|
1973 |
|
1974 StatusUpdate("Downloading data records.") |
|
1975 |
|
1976 args = self._MakeLoaderArgs() |
|
1977 args['download'] = True |
|
1978 args['has_header'] = False |
|
1979 |
|
1980 run_fn(args) |
|
1981 |
|
1982 def PerformUpload(self, run_fn=None): |
|
1983 """Performs a datastore upload via the bulkloader. |
|
1984 |
|
1985 Args: |
|
1986 run_fn: Function to invoke the bulkloader, used for testing. |
|
1987 """ |
|
1988 if run_fn is None: |
|
1989 run_fn = self.RunBulkloader |
|
1990 self._SetupLoad() |
|
1991 |
1937 StatusUpdate("Uploading data records.") |
1992 StatusUpdate("Uploading data records.") |
1938 |
1993 |
1939 run_fn(app_id=self.options.app_id, |
1994 args = self._MakeLoaderArgs() |
1940 url=self.options.url, |
1995 args['download'] = False |
1941 filename=self.options.filename, |
1996 |
1942 batch_size=self.options.batch_size, |
1997 run_fn(args) |
1943 kind=self.options.kind, |
1998 |
1944 num_threads=self.options.num_threads, |
1999 def _PerformLoadOptions(self, parser): |
1945 bandwidth_limit=self.options.bandwidth_limit, |
2000 """Adds options common to 'upload_data' and 'download_data'. |
1946 rps_limit=self.options.rps_limit, |
|
1947 http_limit=self.options.http_limit, |
|
1948 db_filename=self.options.db_filename, |
|
1949 config_file=self.options.config_file, |
|
1950 auth_domain=self.options.auth_domain, |
|
1951 has_header=self.options.has_header, |
|
1952 loader_opts=self.options.loader_opts, |
|
1953 log_file=self.options.log_file, |
|
1954 passin=self.options.passin, |
|
1955 email=self.options.email, |
|
1956 debug=self.options.debug, |
|
1957 |
|
1958 exporter_opts=None, |
|
1959 download=False, |
|
1960 result_db_filename=None, |
|
1961 ) |
|
1962 |
|
1963 def _PerformUploadOptions(self, parser): |
|
1964 """Adds 'upload_data' specific options to the 'parser' passed in. |
|
1965 |
2001 |
1966 Args: |
2002 Args: |
1967 parser: An instance of OptionsParser. |
2003 parser: An instance of OptionsParser. |
1968 """ |
2004 """ |
1969 parser.add_option("--filename", type="string", dest="filename", |
2005 parser.add_option("--filename", type="string", dest="filename", |
1998 action="store", |
2034 action="store", |
1999 help="Name of the progress database file.") |
2035 help="Name of the progress database file.") |
2000 parser.add_option("--auth_domain", type="string", dest="auth_domain", |
2036 parser.add_option("--auth_domain", type="string", dest="auth_domain", |
2001 action="store", default="gmail.com", |
2037 action="store", default="gmail.com", |
2002 help="The name of the authorization domain to use.") |
2038 help="The name of the authorization domain to use.") |
|
2039 parser.add_option("--log_file", type="string", dest="log_file", |
|
2040 help="File to write bulkloader logs. If not supplied " |
|
2041 "then a new log file will be created, named: " |
|
2042 "bulkloader-log-TIMESTAMP.") |
|
2043 |
|
2044 def _PerformUploadOptions(self, parser): |
|
2045 """Adds 'upload_data' specific options to the 'parser' passed in. |
|
2046 |
|
2047 Args: |
|
2048 parser: An instance of OptionsParser. |
|
2049 """ |
|
2050 self._PerformLoadOptions(parser) |
2003 parser.add_option("--has_header", dest="has_header", |
2051 parser.add_option("--has_header", dest="has_header", |
2004 action="store_true", default=False, |
2052 action="store_true", default=False, |
2005 help="Whether the first line of the input file should be" |
2053 help="Whether the first line of the input file should be" |
2006 " skipped") |
2054 " skipped") |
2007 parser.add_option("--loader_opts", type="string", dest="loader_opts", |
2055 parser.add_option("--loader_opts", type="string", dest="loader_opts", |
2008 help="A string to pass to the Loader.Initialize method.") |
2056 help="A string to pass to the Loader.initialize method.") |
2009 parser.add_option("--log_file", type="string", dest="log_file", |
2057 |
2010 help="File to write bulkloader logs. If not supplied " |
2058 def _PerformDownloadOptions(self, parser): |
2011 "then a new log file will be created, named: " |
2059 """Adds 'download_data' specific options to the 'parser' passed in. |
2012 "bulkloader-log-TIMESTAMP.") |
2060 |
|
2061 Args: |
|
2062 parser: An instance of OptionsParser. |
|
2063 """ |
|
2064 self._PerformLoadOptions(parser) |
|
2065 parser.add_option("--exporter_opts", type="string", dest="exporter_opts", |
|
2066 help="A string to pass to the Exporter.initialize method." |
|
2067 ) |
|
2068 parser.add_option("--result_db_filename", type="string", |
|
2069 dest="result_db_filename", |
|
2070 action="store", |
|
2071 help="Database to write entities to for download.") |
2013 |
2072 |
2014 class Action(object): |
2073 class Action(object): |
2015 """Contains information about a command line action. |
2074 """Contains information about a command line action. |
2016 |
2075 |
2017 Attributes: |
2076 Attributes: |
2119 |
2178 |
2120 "upload_data": Action( |
2179 "upload_data": Action( |
2121 function="PerformUpload", |
2180 function="PerformUpload", |
2122 usage="%prog [options] upload_data <directory>", |
2181 usage="%prog [options] upload_data <directory>", |
2123 options=_PerformUploadOptions, |
2182 options=_PerformUploadOptions, |
2124 short_desc="Upload CSV records to datastore", |
2183 short_desc="Upload data records to datastore.", |
2125 long_desc=""" |
2184 long_desc=""" |
2126 The 'upload_data' command translates CSV records into datastore entities and |
2185 The 'upload_data' command translates input records into datastore entities and |
2127 uploads them into your application's datastore."""), |
2186 uploads them into your application's datastore."""), |
|
2187 |
|
2188 "download_data": Action( |
|
2189 function="PerformDownload", |
|
2190 usage="%prog [options] download_data <directory>", |
|
2191 options=_PerformDownloadOptions, |
|
2192 short_desc="Download entities from datastore.", |
|
2193 long_desc=""" |
|
2194 The 'download_data' command downloads datastore entities and writes them to |
|
2195 file as CSV or developer defined format."""), |
2128 |
2196 |
2129 |
2197 |
2130 |
2198 |
2131 } |
2199 } |
2132 |
2200 |