blob: 68a572fad12b6fbd348db1213ed2a68d8e36243c [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.
# Make sure Python loads the modules of this package via absolute paths.
from os.path import abspath as _abspath
from gppylib import gplog
__path__[0] = _abspath(__path__[0])
logger = gplog.get_default_logger()
class Operation(object):
"""
An Operation (abstract class) is one atomic unit of work.
An implementation of Operation is fully defined by:
__init__(self, opts, args) - opts, args are intended to be validated and marshaled, as appropriate
execute(self) - the actual Operation logic commences
cleanup(self) - [optional] any cleanup operations; e.g. stopping a WorkerPool
undo(self) - ?
progress(self) - ?
validate(self) - ?
cancel(self) - ?
By decomposing work into these Operation classes whenever possible,
we should be able to reap several benefits:
1. Unit testing - It's easier to build unit tests around functions
that are not tightly bound to a monolithic class
2. Composition - We should be able to compose Operations in a useful manner.
Consider ParallelOperation, RemoteOperation, SerialOperation, etc.
3. Readability - We no longer have to create nested, segment-facing scripts
to perform work on segment hosts: e.g. figuring out how
gpstart -> startSegments -> gpsegstart.py propagates information is
unnecessarily confusing.
4. Reusability - This makes core functionality more accessible to other clients
of this code; e.g. a Python web backend
5. Complexity - It's easier to grasp what an Operation does by examining
the flowchart of Operations it invokes
Caveat: Operations cannot return Exceptions. RemoteOperation and ParallelOperation
would interpret that as a raised Exception.
"""
def __init__(self):
self.ret = None
def execute(self):
raise NotImplementedError("Operation.execute(self) must be implemented.")
def cleanup(self):
pass
def run(self):
# TODO: logging, just like Program.run
# TODO: async argument to allow caller to monitor or to provide progress updates
logger.debug('Starting %s' % self.__class__.__name__)
try:
self.ret = self.execute()
return self.ret
except Exception, e:
self.ret = e
raise
finally:
self.cleanup()
logger.debug('Ending %s' % self.__class__.__name__)
def __str__(self):
return self.__class__.__name__
def get_ret(self):
if isinstance(self.ret, Exception):
raise self.ret
return self.ret