Skip to content

Commit 1eb0f84

Browse files
committed
Merge branch 'release-1.6.0'
2 parents 510be62 + a1ec1b2 commit 1eb0f84

28 files changed

+1637
-620
lines changed

.travis.yml

+2-3
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ language: python
22
python:
33
- "2.6"
44
- "2.7"
5-
- "3.4"
5+
- "3.5"
66
matrix:
77
allow_failures:
8-
- python: "2.7"
9-
- python: "3.4"
8+
- python: "3.5"
109
fast_finish: true
1110

1211
# Route build to container-based infrastructure

CHANGELOG

+12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
Changelog for apel
22
==================
3+
* Fri Mar 10 2017 Adrian Coveney <[email protected]> - 1.6.0-1
4+
- Added support for mixed time formats used in Torque 5.1.3.
5+
- Changed the way core count is parsed to support Torque 5.1.0.
6+
- Added CPU count to cloud accounting summaries.
7+
- Added support for cloud accounting schema v0.4 format records.
8+
- Added SQL file to update cloud schema.
9+
- Removed file attribute args from the create directive in logrotate script.
10+
- Fixed crashes of storage accounting loader due to newer format records.
11+
- Added support for paged GOCDB API results.
12+
- Disabled duplicate sites check in server summarising process.
13+
- Updated partitioning in server-extra.sql.
14+
315
* Tue Jan 12 2016 Adrian Coveney <[email protected]> - 1.5.1-1
416
- Add support for Torque 5.1.2 time duration format.
517
- Change dirq call to use absolute path to support versions of dirq >= 1.7.

apel.spec

+21-5
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
%endif
55

66
Name: apel
7-
Version: 1.5.1
7+
Version: 1.6.0
88
%define releasenumber 1
99
Release: %{releasenumber}%{?dist}
1010
Summary: APEL packages
@@ -34,7 +34,7 @@ apel-lib provides required libraries for the rest of APEL system.
3434
%package parsers
3535
Summary: Parsers for APEL system
3636
Group: Development/Languages
37-
Requires: apel-lib >= 1.5.1
37+
Requires: apel-lib >= 1.6.0
3838
Requires(pre): shadow-utils
3939

4040
%description parsers
@@ -44,7 +44,7 @@ supported by the APEL system: Torque, SGE and LSF.
4444
%package client
4545
Summary: APEL client package
4646
Group: Development/Languages
47-
Requires: apel-lib >= 1.5.1, apel-ssm
47+
Requires: apel-lib >= 1.6.0, apel-ssm
4848
Requires(pre): shadow-utils
4949

5050
%description client
@@ -55,7 +55,7 @@ SSM.
5555
%package server
5656
Summary: APEL server package
5757
Group: Development/Languages
58-
Requires: apel-lib >= 1.5.1, apel-ssm
58+
Requires: apel-lib >= 1.6.0, apel-ssm
5959
Requires(pre): shadow-utils
6060

6161
%description server
@@ -109,6 +109,8 @@ cp schemas/server-extra.sql %{buildroot}%_datadir/apel/
109109
cp schemas/cloud.sql %{buildroot}%_datadir/apel/
110110
cp schemas/storage.sql %{buildroot}%_datadir/apel/
111111

112+
cp scripts/update_schema.sql %{buildroot}%_datadir/apel/
113+
112114
# accounting scripts
113115
cp scripts/slurm_acc.sh %{buildroot}%_datadir/apel/
114116
cp scripts/htcondor_acc.sh %{buildroot}%_datadir/apel/
@@ -169,7 +171,9 @@ exit 0
169171
%_datadir/apel/server-extra.sql
170172
%_datadir/apel/cloud.sql
171173
%_datadir/apel/storage.sql
172-
%attr(755,root,root) %_datadir/apel/msg_status.py
174+
%_datadir/apel/update_schema.sql
175+
# Use wildcard to match .py, .pyc, and .pyo
176+
%attr(755,root,root) %_datadir/apel/msg_status.py*
173177

174178
# Directories for logs, PID files
175179
%dir %{_localstatedir}/log/apel
@@ -189,6 +193,18 @@ exit 0
189193
# ==============================================================================
190194

191195
%changelog
196+
* Fri Mar 10 2017 Adrian Coveney <[email protected]> - 1.6.0-1
197+
- Added support for mixed time formats used in Torque 5.1.3.
198+
- Changed the way core count is parsed to support Torque 5.1.0.
199+
- Added CPU count to cloud accounting summaries.
200+
- Added support for cloud accounting schema v0.4 format records.
201+
- Added SQL file to update cloud schema.
202+
- Removed file attribute args from the create directive in logrotate script.
203+
- Fixed crashes of storage accounting loader due to newer format records.
204+
- Added support for paged GOCDB API results.
205+
- Disabled duplicate sites check in server summarising process.
206+
- Updated partitioning in server-extra.sql.
207+
192208
* Tue Jan 12 2016 Adrian Coveney <[email protected]> - 1.5.1-1
193209
- Add support for Torque 5.1.2 time duration format.
194210
- Change dirq call to use absolute path to support versions of dirq >= 1.7.

apel/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@
1515
1616
@author Konrad Jopek, Will Rogers
1717
'''
18-
__version__ = (1, 5, 1)
18+
__version__ = (1, 6, 0)

apel/db/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
SUMMARY_MSG_HEADER = "APEL-summary-job-message: v0.2"
2020
NORMALISED_SUMMARY_MSG_HEADER = "APEL-summary-job-message: v0.3"
2121
SYNC_MSG_HEADER = "APEL-sync-message: v0.1"
22-
CLOUD_MSG_HEADER = 'APEL-cloud-message: v0.2'
23-
CLOUD_SUMMARY_MSG_HEADER = 'APEL-cloud-summary-message: v0.2'
22+
CLOUD_MSG_HEADER = 'APEL-cloud-message: v0.4'
23+
CLOUD_SUMMARY_MSG_HEADER = 'APEL-cloud-summary-message: v0.4'
2424

2525
from apel.db.apeldb import ApelDb, Query, ApelDbException

apel/db/backends/mysql.py

+19-6
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@ class ApelMysqlDb(object):
6868
NormalisedSummaryRecord: "CALL ReplaceNormalisedSummary(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",
6969
SyncRecord : "CALL ReplaceSyncRecord(%s, %s, %s, %s, %s, %s)",
7070
ProcessedRecord : "CALL ReplaceProcessedFile(%s, %s, %s, %s, %s)",
71-
CloudRecord : "CALL ReplaceCloudRecord(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",
72-
CloudSummaryRecord : "CALL ReplaceCloudSummaryRecord(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",
73-
StorageRecord: "CALL ReplaceStarRecord(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",
71+
CloudRecord : "CALL ReplaceCloudRecord(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",
72+
CloudSummaryRecord : "CALL ReplaceCloudSummaryRecord(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",
73+
StorageRecord: "CALL ReplaceStarRecord(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",
7474
GroupAttributeRecord: "CALL ReplaceGroupAttribute(%s, %s, %s)"
7575
}
7676

@@ -122,13 +122,22 @@ def test_connection(self):
122122

123123
def load_records(self, record_list, replace=True, source=None):
124124
'''
125-
Loads the records in the list into the DB. This is transactional -
126-
either all or no records will be loaded. Includes the DN of the
125+
Loads the records in the list into the DB. This is transactional -
126+
either all or no records will be loaded. Includes the DN of the
127127
sender.
128128
'''
129-
# all records in the list are the same type
129+
# All records in the list should be of the same type (but may not be),
130+
# unless they are Storage or GroupAttribute records which can be mixed.
130131
try:
131132
record_type = type(record_list[0])
133+
134+
# Check that all the records are the same type as the first (except
135+
# for Storage and GroupAttribute records).
136+
for record in record_list:
137+
if (type(record) != record_type and type(record) not in
138+
(StorageRecord, GroupAttributeRecord)):
139+
raise ApelDbException("Not all records in list are of type %s." % record_type)
140+
132141
if replace:
133142
proc = self.REPLACE_PROCEDURES[record_type]
134143
else:
@@ -149,6 +158,10 @@ def load_records(self, record_list, replace=True, source=None):
149158
for record in record_list:
150159
values = record.get_db_tuple(source)
151160
log.debug('Values: %s', values)
161+
if type(record) in (StorageRecord, GroupAttributeRecord):
162+
# These types can be found in the same record list, so need
163+
# to get the right proedure for each one.
164+
proc = self.REPLACE_PROCEDURES[type(record)]
152165
c.execute(proc, values)
153166
self.db.commit()
154167
except (MySQLdb.Warning, MySQLdb.Error, KeyError), err:

apel/db/loader/star_parser.py

+90-76
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@
1212
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
See the License for the specific language governing permissions and
1414
limitations under the License.
15-
16-
@author: Konrad Jopek
15+
1716
'''
1817

1918

@@ -31,24 +30,25 @@
3130
class StarParser(XMLParser):
3231
'''
3332
Parser for Storage Accounting Records
34-
35-
For documentation please visit:
33+
34+
For documentation please visit:
3635
https://twiki.cern.ch/twiki/bin/view/EMI/StorageAccounting
3736
'''
38-
37+
3938
NAMESPACE = "http://eu-emi.eu/namespaces/2011/02/storagerecord"
40-
39+
4140
def get_records(self):
42-
'''
41+
"""
4342
Returns list of parsed records from STAR file.
44-
43+
4544
Please notice that this parser _requires_ valid
4645
structure of XML document, including namespace
4746
information and prefixes in XML tag (like urf:StorageUsageRecord).
48-
'''
47+
"""
4948
records = []
50-
51-
xml_storage_records = self.doc.getElementsByTagNameNS(self.NAMESPACE, 'StorageUsageRecord')
49+
50+
xml_storage_records = self.doc.getElementsByTagNameNS(
51+
self.NAMESPACE, 'StorageUsageRecord')
5252

5353
if len(xml_storage_records) == 0:
5454
raise XMLParserException('File does not contain StAR records!')
@@ -61,69 +61,80 @@ def get_records(self):
6161
records += group_attributes
6262

6363
return records
64-
64+
6565
def parseStarRecord(self, xml_storage_record):
66-
'''
66+
"""
6767
Parses single entry for Storage Accounting Record.
68-
The dictionary below contains fields from record and
69-
methods to extract them from XML file.
70-
'''
71-
68+
69+
Uses a dictionary containing fields from the storage record and methods
70+
to extract them from the XML. Returns a list of StorageRecords (plus
71+
GroupAttributeRecords if there are GroupAttributes that have an
72+
attributeType that is NOT subgroup or role).
73+
"""
74+
7275
functions = {
73-
'RecordId' : lambda nodes: self.getAttr(nodes['RecordIdentity'][0],
74-
'recordId'),
75-
'CreateTime' : lambda nodes: parse_timestamp(
76-
self.getAttr(nodes['RecordIdentity'][0],
77-
'createTime')),
78-
'StorageSystem' : lambda nodes: self.getText(
79-
nodes['StorageSystem'][0].childNodes),
80-
'Site' : lambda nodes: self.getText(
81-
nodes['Site'][0].childNodes),
82-
83-
'StorageShare' : lambda nodes: self.getText(
84-
nodes['StorageShare'][0].childNodes),
85-
'StorageMedia' : lambda nodes: self.getText(
86-
nodes['StorageMedia'][0].childNodes),
87-
'StorageClass' : lambda nodes: self.getText(
88-
nodes['StorageClass'][0].childNodes),
89-
'FileCount' : lambda nodes: self.getText(
90-
nodes['FileCount'][0].childNodes),
91-
'DirectoryPath' : lambda nodes: self.getText(
92-
nodes['DirectoryPath'][0].childNodes),
93-
'LocalUser' : lambda nodes: self.getText(
94-
nodes['LocalUser'][0].childNodes),
95-
'LocalGroup' : lambda nodes: self.getText(
96-
nodes['LocalGroup'][0].childNodes),
97-
'UserIdentity' : lambda nodes: self.getText(
98-
nodes['UserIdentity'][0].childNodes),
99-
'Group' : lambda nodes: self.getText(
100-
nodes['Group'][0].childNodes),
101-
'StartTime' : lambda nodes: parse_timestamp(self.getText(nodes['StartTime'][0].childNodes)),
102-
'EndTime' : lambda nodes: parse_timestamp(self.getText(nodes['EndTime'][0].childNodes)),
103-
'ResourceCapacityUsed' : lambda nodes: self.getText(
76+
'RecordId': lambda nodes: self.getAttr(
77+
nodes['RecordIdentity'][0], 'recordId'),
78+
'CreateTime': lambda nodes: parse_timestamp(self.getAttr(
79+
nodes['RecordIdentity'][0], 'createTime')),
80+
'StorageSystem': lambda nodes: self.getText(
81+
nodes['StorageSystem'][0].childNodes),
82+
'Site': lambda nodes: self.getText(
83+
nodes['Site'][0].childNodes),
84+
'StorageShare': lambda nodes: self.getText(
85+
nodes['StorageShare'][0].childNodes),
86+
'StorageMedia': lambda nodes: self.getText(
87+
nodes['StorageMedia'][0].childNodes),
88+
'StorageClass': lambda nodes: self.getText(
89+
nodes['StorageClass'][0].childNodes),
90+
'FileCount': lambda nodes: self.getText(
91+
nodes['FileCount'][0].childNodes),
92+
'DirectoryPath': lambda nodes: self.getText(
93+
nodes['DirectoryPath'][0].childNodes),
94+
'LocalUser': lambda nodes: self.getText(
95+
nodes['LocalUser'][0].childNodes),
96+
'LocalGroup': lambda nodes: self.getText(
97+
nodes['LocalGroup'][0].childNodes),
98+
'UserIdentity': lambda nodes: self.getText(
99+
nodes['UserIdentity'][0].childNodes),
100+
'Group': lambda nodes: self.getText(
101+
nodes['Group'][0].childNodes),
102+
'SubGroup': lambda nodes: self.getText(self.getTagByAttr(
103+
nodes['SubGroup'], 'attributeType', 'subgroup')[0].childNodes),
104+
'Role': lambda nodes: self.getText(self.getTagByAttr(
105+
nodes['Role'], 'attributeType', 'role')[0].childNodes),
106+
'StartTime': lambda nodes: parse_timestamp(self.getText(
107+
nodes['StartTime'][0].childNodes)),
108+
'EndTime': lambda nodes: parse_timestamp(self.getText(
109+
nodes['EndTime'][0].childNodes)),
110+
'ResourceCapacityUsed': lambda nodes: self.getText(
104111
nodes['ResourceCapacityUsed'][0].childNodes),
105-
'LogicalCapacityUsed' : lambda nodes: self.getText(
112+
'LogicalCapacityUsed': lambda nodes: self.getText(
106113
nodes['LogicalCapacityUsed'][0].childNodes),
107-
'ResourceCapacityAllocated' : lambda nodes: self.getText(
114+
'ResourceCapacityAllocated': lambda nodes: self.getText(
108115
nodes['ResourceCapacityAllocated'][0].childNodes)
109-
}
116+
}
110117

111-
# here we copy keys from functions
112-
# we only want to change 'RecordId' to 'RecordIdentity',
113-
nodes = {}.fromkeys( map (lambda f: f == 'RecordId' and 'RecordIdentity' or f,
114-
[S for S in functions]) )
118+
# Here we copy keys from functions.
119+
# We only want to change 'RecordId' to 'RecordIdentity'.
120+
nodes = {}.fromkeys(map(lambda f: f == 'RecordId' and
121+
'RecordIdentity' or f, [S for S in functions]))
115122
# nodes = {}.fromkeys(functions.keys())
116123
data = {}
117124

118125
for node in nodes:
126+
if node in ('SubGroup', 'Role'):
127+
# For these attributes we need to dig into the GroupAttribute
128+
# elements to get the values so we save the whole elements.
129+
nodes[node] = xml_storage_record.getElementsByTagNameNS(
130+
self.NAMESPACE, 'GroupAttribute')
131+
else:
132+
nodes[node] = xml_storage_record.getElementsByTagNameNS(
133+
self.NAMESPACE, node)
119134
# empty list = element have not been found in XML file
120-
nodes[node] = xml_storage_record.getElementsByTagNameNS(self.NAMESPACE, node)
121-
135+
122136
for field in functions:
123137
try:
124-
# if field == 'Group':
125-
# data['GroupName'] = functions[field](nodes)
126-
# else:
127138
data[field] = functions[field](nodes)
128139
except (IndexError, KeyError), e:
129140
log.debug("Failed to get field %s: %s", field, e)
@@ -132,24 +143,27 @@ def parseStarRecord(self, xml_storage_record):
132143
sr.set_all(data)
133144

134145
return sr, self.parseGroupAttributes(
135-
xml_storage_record.getElementsByTagNameNS(
136-
self.NAMESPACE,'GroupAttribute'),
137-
sr.get_field('RecordId'))
138-
139-
146+
xml_storage_record.getElementsByTagNameNS(
147+
self.NAMESPACE, 'GroupAttribute'), sr.get_field('RecordId'))
148+
140149
def parseGroupAttributes(self, nodes, star_record_id):
141-
'''
142-
Return a list of GroupAttributes associated with StarRecord.
143-
'''
150+
"""
151+
Return a list of GroupAttributeRecords associated with a StarRecord.
152+
153+
Only returns records for attributeTypes other than subgroup and role as
154+
those two types are stored in the StarRecord.
155+
"""
144156
ret = []
145157

146158
for node in nodes:
147-
group_attr = GroupAttributeRecord()
148-
group_attr.set_field('StarRecordID', star_record_id)
149159
attr_type = self.getAttr(node, 'attributeType')
150-
group_attr.set_field('AttributeType', attr_type)
151-
attr_value = self.getText(node.childNodes)
152-
group_attr.set_field('AttributeValue', attr_value)
153-
ret.append(group_attr)
154-
155-
return ret
160+
# Only create records for types other than subgroup and role
161+
if attr_type not in ('subgroup', 'role'):
162+
group_attr = GroupAttributeRecord()
163+
group_attr.set_field('StarRecordID', star_record_id)
164+
group_attr.set_field('AttributeType', attr_type)
165+
attr_value = self.getText(node.childNodes)
166+
group_attr.set_field('AttributeValue', attr_value)
167+
ret.append(group_attr)
168+
169+
return ret

0 commit comments

Comments
 (0)