mirror of
https://github.com/xcat2/confluent.git
synced 2024-11-26 03:19:48 +00:00
485c323608
The strategy of duping file descriptors is inadequate. The copies share identical offsets. Fix this by reading the file once into memory, and using BytesIO to fake a file. This is relatively memory intensive in theory, but in practice pyghmi library had been duping everything to memory anyway, so it is a wash for now.
206 lines
7.8 KiB
Python
206 lines
7.8 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
|
|
# Copyright 2017 Lenovo
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
# provide managing firmware update process and firmware repository if/when
|
|
# the time comes
|
|
|
|
import confluent.exceptions as exc
|
|
import confluent.log as log
|
|
import confluent.messages as msg
|
|
import eventlet
|
|
import io
|
|
import os
|
|
import pwd
|
|
import socket
|
|
import traceback
|
|
|
|
updatesbytarget = {}
|
|
uploadsbytarget = {}
|
|
downloadsbytarget = {}
|
|
updatepool = eventlet.greenpool.GreenPool(256)
|
|
_tracelog = None
|
|
sharedfiles = {}
|
|
|
|
|
|
def execupdate(handler, filename, updateobj, type, owner, node, datfile):
|
|
global _tracelog
|
|
try:
|
|
if type != 'ffdc' and not datfile:
|
|
errstr = False
|
|
if not os.path.exists(filename):
|
|
errstr = '{0} does not appear to exist on {1}, or is in a directory with permissions forbidding confluent user/group access'.format(
|
|
filename, socket.gethostname())
|
|
elif not os.access(filename, os.R_OK):
|
|
errstr = '{0} is not readable by confluent on {1} (ensure confluent user or group can access file and parent directories)'.format(
|
|
filename, socket.gethostname())
|
|
if errstr:
|
|
updateobj.handle_progress({'phase': 'error', 'progress': 0.0,
|
|
'detail': errstr})
|
|
return
|
|
if type == 'ffdc' and os.path.isdir(filename):
|
|
filename += '/' + node
|
|
if 'type' == 'ffdc':
|
|
errstr = False
|
|
if os.path.exists(filename):
|
|
errstr = '{0} already exists on {1}, cannot overwrite'.format(
|
|
filename, socket.gethostname())
|
|
elif not os.access(os.path.dirname(filename), os.W_OK):
|
|
errstr = '{0} directory not writable by confluent user/group on {1}, check the directory and parent directory ownership and permissions'.format(filename, socket.gethostname())
|
|
if errstr:
|
|
updateobj.handle_progress({'phase': 'error', 'progress': 0.0,
|
|
'detail': errstr})
|
|
return
|
|
try:
|
|
if type == 'firmware':
|
|
completion = handler(filename, progress=updateobj.handle_progress,
|
|
data=datfile, bank=updateobj.bank)
|
|
else:
|
|
completion = handler(filename, progress=updateobj.handle_progress,
|
|
data=datfile)
|
|
if type == 'ffdc' and completion:
|
|
filename = completion
|
|
completion = None
|
|
if completion is None:
|
|
completion = 'complete'
|
|
if owner:
|
|
pwent = pwd.getpwnam(owner)
|
|
os.chown(filename, pwent.pw_uid, pwent.pw_gid)
|
|
updateobj.handle_progress({'phase': completion, 'progress': 100.0})
|
|
except exc.PubkeyInvalid as pi:
|
|
errstr = 'Certificate mismatch detected, does not match value in ' \
|
|
'attribute {0}'.format(pi.attrname)
|
|
updateobj.handle_progress({'phase': 'error', 'progress': 0.0,
|
|
'detail': errstr})
|
|
except Exception as e:
|
|
if _tracelog is None:
|
|
_tracelog = log.Logger('trace')
|
|
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event, event=log.Events.stacktrace)
|
|
updateobj.handle_progress({'phase': 'error', 'progress': 0.0,
|
|
'detail': str(e)})
|
|
finally:
|
|
if filename in sharedfiles:
|
|
if sharedfiles[filename][0] == 1:
|
|
del sharedfiles[filename]
|
|
else:
|
|
sharedfiles[filename][0] -= 1
|
|
|
|
|
|
class Updater(object):
|
|
def __init__(self, node, handler, filename, tenant=None, name=None,
|
|
bank=None, type='firmware', owner=None, configmanager=None):
|
|
self.bank = bank
|
|
self.node = node
|
|
self.phase = 'initializing'
|
|
self.detail = ''
|
|
self.percent = 0.0
|
|
if configmanager and filename in configmanager.clientfiles:
|
|
if filename in sharedfiles:
|
|
sharedfiles[filename][0] += 1
|
|
else:
|
|
cf = configmanager.clientfiles[filename]
|
|
datfile = os.fdopen(os.dup(cf.fileno()), cf.mode)
|
|
sharedfiles[filename] = [1, datfile.read()]
|
|
datfile.close()
|
|
datfile = io.BytesIO(sharedfiles[filename][1])
|
|
else:
|
|
datfile = None
|
|
self.datfile = datfile
|
|
self.updateproc = updatepool.spawn(execupdate, handler, filename,
|
|
self, type, owner, node, datfile)
|
|
if type == 'firmware':
|
|
myparty = updatesbytarget
|
|
elif type == 'mediaupload':
|
|
myparty = uploadsbytarget
|
|
elif type == 'ffdc':
|
|
myparty = downloadsbytarget
|
|
if (node, tenant) not in myparty:
|
|
myparty[(node, tenant)] = {}
|
|
if name is None:
|
|
name = 1
|
|
while '{0}'.format(name) in myparty[(node, tenant)]:
|
|
name += 1
|
|
self.name = '{0}'.format(name)
|
|
myparty[(node, tenant)][self.name] = self
|
|
|
|
def handle_progress(self, progress):
|
|
self.phase = progress.get('phase', 'unknown')
|
|
self.percent = float(progress.get('progress', 100.0))
|
|
self.detail = progress.get('detail', '')
|
|
|
|
def cancel(self):
|
|
self.updateproc.kill()
|
|
if self.datfile:
|
|
self.datfile.close()
|
|
|
|
@property
|
|
def progress(self):
|
|
return {'phase': self.phase, 'progress': self.percent,
|
|
'detail': self.detail}
|
|
|
|
|
|
def remove_updates(nodes, tenant, element, type='firmware'):
|
|
if len(element) < 5 and element[:2] not in (['media', 'uploads'], ['support', 'servicedata']):
|
|
raise exc.InvalidArgumentException()
|
|
upid = element[-1]
|
|
if type == 'firmware':
|
|
myparty = updatesbytarget
|
|
elif type == 'ffdc':
|
|
myparty = downloadsbytarget
|
|
else:
|
|
myparty = uploadsbytarget
|
|
for node in nodes:
|
|
try:
|
|
upd = myparty[(node, tenant)][upid]
|
|
except KeyError:
|
|
raise exc.NotFoundException('No active update matches request')
|
|
upd.cancel()
|
|
del myparty[(node, tenant)][upid]
|
|
yield msg.DeletedResource(
|
|
'nodes/{0}/inventory/firmware/updates/active/{1}'.format(
|
|
node, upid))
|
|
|
|
|
|
def list_updates(nodes, tenant, element, type='firmware'):
|
|
showmode = False
|
|
if type == 'mediaupload':
|
|
myparty = uploadsbytarget
|
|
verb = 'upload'
|
|
elif type == 'ffdc':
|
|
verb = 'download'
|
|
myparty = downloadsbytarget
|
|
else:
|
|
myparty = updatesbytarget
|
|
verb = 'update'
|
|
if type == 'firmware':
|
|
specificlen = 4
|
|
else:
|
|
specificlen = 2
|
|
if len(element) > specificlen:
|
|
showmode = True
|
|
upid = element[-1]
|
|
for node in nodes:
|
|
if showmode:
|
|
try:
|
|
updater = myparty[(node, tenant)][upid]
|
|
except KeyError:
|
|
raise exc.NotFoundException(
|
|
'No matching {0} process found'.format(verb))
|
|
yield msg.KeyValueData(updater.progress, name=node)
|
|
else:
|
|
for updateid in myparty.get((node, tenant), {}):
|
|
yield msg.ChildCollection(updateid)
|