1 # copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
|
2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|
3 # |
|
4 # This file is part of CubicWeb. |
|
5 # |
|
6 # CubicWeb is free software: you can redistribute it and/or modify it under the |
|
7 # terms of the GNU Lesser General Public License as published by the Free |
|
8 # Software Foundation, either version 2.1 of the License, or (at your option) |
|
9 # any later version. |
|
10 # |
|
11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT |
|
12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
|
13 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
|
14 # details. |
|
15 # |
|
16 # You should have received a copy of the GNU Lesser General Public License along |
|
17 # with CubicWeb. If not, see <http://www.gnu.org/licenses/>. |
|
18 """Define server side service provided by cubicweb""" |
|
19 |
|
20 import threading |
|
21 |
|
22 from six import text_type |
|
23 |
|
24 from yams.schema import role_name |
|
25 |
|
26 from cubicweb import ValidationError |
|
27 from cubicweb.server import Service |
|
28 from cubicweb.predicates import match_user_groups, match_kwargs |
|
29 |
|
30 class StatsService(Service): |
|
31 """Return a dictionary containing some statistics about the repository |
|
32 resources usage. |
|
33 """ |
|
34 |
|
35 __regid__ = 'repo_stats' |
|
36 __select__ = match_user_groups('managers', 'users') |
|
37 |
|
38 def call(self): |
|
39 repo = self._cw.repo # Service are repo side only. |
|
40 results = {} |
|
41 querier = repo.querier |
|
42 source = repo.system_source |
|
43 for size, maxsize, hits, misses, title in ( |
|
44 (len(querier._rql_cache), repo.config['rql-cache-size'], |
|
45 querier.cache_hit, querier.cache_miss, 'rqlt_st'), |
|
46 (len(source._cache), repo.config['rql-cache-size'], |
|
47 source.cache_hit, source.cache_miss, 'sql'), |
|
48 ): |
|
49 results['%s_cache_size' % title] = {'size': size, 'maxsize': maxsize} |
|
50 results['%s_cache_hit' % title] = hits |
|
51 results['%s_cache_miss' % title] = misses |
|
52 results['%s_cache_hit_percent' % title] = (hits * 100) / (hits + misses) |
|
53 results['type_source_cache_size'] = len(repo._type_source_cache) |
|
54 results['extid_cache_size'] = len(repo._extid_cache) |
|
55 results['sql_no_cache'] = repo.system_source.no_cache |
|
56 results['nb_open_sessions'] = len(repo._sessions) |
|
57 results['nb_active_threads'] = threading.activeCount() |
|
58 looping_tasks = repo._tasks_manager._looping_tasks |
|
59 results['looping_tasks'] = [(t.name, t.interval) for t in looping_tasks] |
|
60 results['available_cnxsets'] = repo._cnxsets_pool.qsize() |
|
61 results['threads'] = [t.name for t in threading.enumerate()] |
|
62 return results |
|
63 |
|
64 |
|
65 class GcStatsService(Service): |
|
66 """Return a dictionary containing some statistics about the repository |
|
67 resources usage. |
|
68 """ |
|
69 |
|
70 __regid__ = 'repo_gc_stats' |
|
71 __select__ = match_user_groups('managers') |
|
72 |
|
73 def call(self, nmax=20): |
|
74 """Return a dictionary containing some statistics about the repository |
|
75 memory usage. |
|
76 |
|
77 This is a public method, not requiring a session id. |
|
78 |
|
79 nmax is the max number of (most) referenced object returned as |
|
80 the 'referenced' result |
|
81 """ |
|
82 |
|
83 from cubicweb._gcdebug import gc_info |
|
84 from cubicweb.appobject import AppObject |
|
85 from cubicweb.rset import ResultSet |
|
86 from cubicweb.web.request import CubicWebRequestBase |
|
87 from rql.stmts import Union |
|
88 |
|
89 lookupclasses = (AppObject, |
|
90 Union, ResultSet, |
|
91 CubicWebRequestBase) |
|
92 try: |
|
93 from cubicweb.server.session import Session, InternalSession |
|
94 lookupclasses += (InternalSession, Session) |
|
95 except ImportError: |
|
96 pass # no server part installed |
|
97 |
|
98 results = {} |
|
99 counters, ocounters, garbage = gc_info(lookupclasses, |
|
100 viewreferrersclasses=()) |
|
101 values = sorted(counters.items(), key=lambda x: x[1], reverse=True) |
|
102 results['lookupclasses'] = values |
|
103 values = sorted(ocounters.items(), key=lambda x: x[1], reverse=True)[:nmax] |
|
104 results['referenced'] = values |
|
105 results['unreachable'] = garbage |
|
106 return results |
|
107 |
|
108 |
|
109 class RegisterUserService(Service): |
|
110 """check if a user with the given login exists, if not create it with the |
|
111 given password. This service is designed to be used for anonymous |
|
112 registration on public web sites. |
|
113 |
|
114 To use it, do: |
|
115 with self.appli.repo.internal_cnx() as cnx: |
|
116 cnx.call_service('register_user', |
|
117 login=login, |
|
118 password=password, |
|
119 **cwuserkwargs) |
|
120 """ |
|
121 __regid__ = 'register_user' |
|
122 __select__ = Service.__select__ & match_kwargs('login', 'password') |
|
123 default_groups = ('users',) |
|
124 |
|
125 def call(self, login, password, email=None, groups=None, **cwuserkwargs): |
|
126 cnx = self._cw |
|
127 errmsg = cnx._('the value "%s" is already used, use another one') |
|
128 |
|
129 if (cnx.execute('CWUser X WHERE X login %(login)s', {'login': login}, |
|
130 build_descr=False) |
|
131 or cnx.execute('CWUser X WHERE X use_email C, C address %(login)s', |
|
132 {'login': login}, build_descr=False)): |
|
133 qname = role_name('login', 'subject') |
|
134 raise ValidationError(None, {qname: errmsg % login}) |
|
135 |
|
136 if isinstance(password, text_type): |
|
137 # password should *always* be utf8 encoded |
|
138 password = password.encode('UTF8') |
|
139 cwuserkwargs['login'] = login |
|
140 cwuserkwargs['upassword'] = password |
|
141 # we have to create the user |
|
142 user = cnx.create_entity('CWUser', **cwuserkwargs) |
|
143 if groups is None: |
|
144 groups = self.default_groups |
|
145 assert groups, "CWUsers must belong to at least one CWGroup" |
|
146 group_names = ', '.join('%r' % group for group in groups) |
|
147 cnx.execute('SET X in_group G WHERE X eid %%(x)s, G name IN (%s)' % group_names, |
|
148 {'x': user.eid}) |
|
149 |
|
150 if email or '@' in login: |
|
151 d = {'login': login, 'email': email or login} |
|
152 if cnx.execute('EmailAddress X WHERE X address %(email)s', d, |
|
153 build_descr=False): |
|
154 qname = role_name('address', 'subject') |
|
155 raise ValidationError(None, {qname: errmsg % d['email']}) |
|
156 cnx.execute('INSERT EmailAddress X: X address %(email)s, ' |
|
157 'U primary_email X, U use_email X ' |
|
158 'WHERE U login %(login)s', d, build_descr=False) |
|
159 |
|
160 return user |
|
161 |
|
162 |
|
163 class SourceSynchronizationService(Service): |
|
164 """Force synchronization of a datafeed source""" |
|
165 __regid__ = 'source-sync' |
|
166 __select__ = Service.__select__ & match_user_groups('managers') |
|
167 |
|
168 def call(self, source_eid): |
|
169 source_entity = self._cw.entity_from_eid(source_eid) |
|
170 repo = self._cw.repo # Service are repo side only. |
|
171 with repo.internal_cnx() as cnx: |
|
172 source = repo.sources_by_uri[source_entity.name] |
|
173 source.pull_data(cnx) |
|
174 |
|