blob: 453f7f2a0b460c1980ba20455bf23964c5900856 [file] [log] [blame]
#
# 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.
#
import threading
import grpc
from ._exceptions import ImplError, RemoteError
# BaseRemote():
#
# Provides the basic functionality required to set up remote
# interaction via GRPC. In particular, this will set up a
# grpc.insecure_channel, or a grpc.secure_channel, based on the given
# spec.
#
# Customization for the particular protocol is expected to be
# performed in children.
#
class BaseRemote:
key_name = None
def __init__(self, spec):
self.spec = spec
self.channel = None
self._initialized = False
self._lock = threading.Lock()
####################################################
# Dunder methods #
####################################################
def __enter__(self):
return self
def __exit__(self, _exc_type, _exc_value, traceback):
self.close()
return False
def __str__(self):
if self.spec:
return self.spec.url
else:
return "(default remote)"
####################################################
# Remote API #
####################################################
# init():
#
# Initialize the given remote. This function must be called before
# any communication is performed, since such will otherwise fail.
#
def init(self):
with self._lock:
if self._initialized:
return
if self.spec:
self.channel = self.spec.open_channel()
self._configure_protocols()
self._initialized = True
def close(self):
if self.channel:
self.channel.close()
self.channel = None
self._initialized = False
# check():
#
# Check if the remote is functional and has all the required
# capabilities. This should be used somewhat like an assertion,
# expecting a RemoteError.
#
# Note that this method runs the calls on a separate process, so
# that we can use grpc calls even if we are on the main process.
#
# Raises:
# RemoteError: If the grpc call fails.
#
def check(self):
try:
self.init()
self._check()
except grpc.RpcError as e:
# str(e) is too verbose for errors reported to the user
raise RemoteError("{}: {}".format(e.code().name, e.details()))
finally:
self.close()
####################################################
# Abstract methods #
####################################################
# _check():
#
# Check if this remote provides everything required for the
# particular kind of remote. This is expected to be called as part
# of check(), and must be called in a non-main process.
#
# Raises:
# RemoteError: when the remote isn't compatible or another error happened.
#
def _check(self):
pass
# _configure_protocols():
#
# An abstract method to configure remote-specific protocols. This
# is *not* done as super().init() because we want to be able to
# set self._initialized *after* initialization completes in the
# parent class.
#
# This method should *never* be called outside of init().
#
def _configure_protocols(self):
raise ImplError("An implementation of a Remote must configure its protocols.")