def __init__(self): """Constructor""" self._bucket = None from Acquire.Service import get_this_service as _get_this_service self._registry_uid = _get_this_service( need_private_access=False).uid()
def _validate_file_upload(par, file_bucket, file_key, objsize, checksum): """Call this function to signify that the file associated with this PAR has been uploaded. This will check that the objsize and checksum match with what was promised """ from Acquire.ObjectStore import ObjectStore as _ObjectStore from Acquire.Service import get_service_account_bucket \ as _get_service_account_bucket from Acquire.Service import get_this_service as _get_this_service service = _get_this_service() bucket = _get_service_account_bucket() file_bucket = _ObjectStore.get_bucket(bucket=bucket, bucket_name=file_bucket, create_if_needed=True) # check that the file uploaded matches what was promised (real_objsize, real_checksum) = _ObjectStore.get_size_and_checksum( file_bucket, file_key) if real_objsize != objsize or real_checksum != checksum: # probably should delete the broken object here... from Acquire.Storage import FileValidationError raise FileValidationError( "The file uploaded does not match what was promised. " "size: %s versus %s, checksum: %s versus %s. Please try " "to upload the file again." % (real_objsize, objsize, real_checksum, checksum))
def __init__(self): """Construct a wallet to hold all user credentials""" from Acquire.Service import is_running_service \ as _is_running_service if _is_running_service(): from Acquire.Service import get_this_service \ as _get_this_service service = _get_this_service(need_private_access=False) raise PermissionError( "You cannot open a Wallet on a running Service (%s)" % service) self._wallet_key = None self._service_info = {} import os as _os wallet_dir = _get_wallet_dir() if not _os.path.exists(wallet_dir): _os.makedirs(wallet_dir, mode=0o700, exist_ok=False) elif not _os.path.isdir(wallet_dir): raise TypeError("The wallet directory must be a directory!") self._wallet_dir = wallet_dir
def submit(worksheet_uid, request, par, secret, cheque): """Submit a job which has; worksheet_uid - """ from Acquire.Service import get_this_service as _get_this_service from Acquire.Access import RunRequest as _RunRequest from Acquire.Client import PAR as _PAR from Acquire.Client import Cheque as _Cheque from Acquire.ObjectStore import create_uid as _create_uid from Acquire.Compute import Cluster as _Cluster if not isinstance(request, _RunRequest): raise TypeError("The request must be type RunRequest") if not isinstance(par, _PAR): raise TypeError("The PAR must be type PAR") if not isinstance(cheque, _Cheque): raise TypeError("The cheque must be type Cheque") service = _get_this_service(need_private_access=True) cluster = _Cluster.get_cluster() job = ComputeJob() job._secret = cluster.encrypt_data(service.decrypt_data(secret)) job._par = par job._request = request cost = 10 # TODO - calculate cost of job again from request try: credit_notes = cheque.cash(spend=cost, resource="work %s" % worksheet_uid) except Exception as e: from Acquire.Service import exception_to_string from Acquire.Accounting import PaymentError raise PaymentError( "Problem cashing the cheque used to pay for the calculation: " "\n\nCAUSE: %s" % exception_to_string(e)) if credit_notes is None or len(credit_notes) == 0: from Acquire.Accounting import PaymentError raise PaymentError("Cannot be paid!") job._credit_notes = credit_notes job._uid = _create_uid(include_date=True, short_uid=True, separator="/") job.save() # signal server running on cluster to fetch job and actually # submit to slurm cluster.submit_job(job._uid) return job
def compute_service(self): """Return the compute service from which this cluster is managed""" if self.is_null(): return None elif Cluster._is_running_service(): from Acquire.Service import get_this_service as _get_this_service return _get_this_service() else: return self._compute_service
def hash(username, password, service_uid=None): """Return a secure hash of the passed username and password""" from Acquire.Crypto import Hash as _Hash from Acquire.Service import get_this_service as _get_this_service if service_uid is None: service_uid = _get_this_service(need_private_access=False).uid() result = _Hash.multi_md5(service_uid, username+password) return result
def compute_service(self): """Return the compute service that will be used to actually perform the calculation associated with this work TODO - will eventually have to choose which compute service to use. Currently returning the compute service that is at the same root URL as this access service """ from Acquire.Service import get_this_service as _get_this_service from Acquire.Service import get_trusted_service as _get_trusted_service service = _get_this_service() compute_url = service.canonical_url().replace("access", "compute") return _get_trusted_service(service_url=compute_url)
def storage_service(self): """Return the storage service that will be used to store the output data associated with this work TODO - will eventually have to choose which storage service to use. Currently returning the storage service that is at the same root URL as this access service """ from Acquire.Service import get_this_service as _get_this_service from Acquire.Service import get_trusted_service as _get_trusted_service service = _get_this_service() storage_url = service.canonical_url().replace("access", "storage") return _get_trusted_service(service_url=storage_url)
def login_url(self): """Return the login URL to login to this session. This is the URL of this identity service plus the short UID of the session """ from Acquire.Service import get_this_service as _get_this_service service = _get_this_service(need_private_access=False) service_uid = service.uid() short_uid = self.short_uid() short_uid = ".".join( [short_uid[i:i + 2] for i in range(0, len(short_uid), 2)]) # eventually allow the service provider to configure this url url = "https://login.acquire-aaai.com" return "%s?id=%s/%s" % (url, service_uid, short_uid)
def get_trusted_services(): """Return a dictionary of all trusted services indexed by their type """ from Acquire.Service import is_running_service as _is_running_service if _is_running_service(): from Acquire.Service import get_this_service as _get_this_service from Acquire.Service import Service as _Service from Acquire.Service import get_service_account_bucket as \ _get_service_account_bucket from Acquire.ObjectStore import ObjectStore as _ObjectStore from Acquire.ObjectStore import url_to_encoded as \ _url_to_encoded # we already trust ourselves service = _get_this_service() trusted_services = {} trusted_services[service.service_type()] = [service] bucket = _get_service_account_bucket() uidkey = "_trusted/uid/" datas = _ObjectStore.get_all_objects(bucket, uidkey) for data in datas: remote_service = _Service.from_data(data) if remote_service.should_refresh_keys(): # need to update the keys in our copy of the service remote_service.refresh_keys() key = "%s/%s" % (uidkey, remote_service.uid()) _ObjectStore.set_object_from_json(bucket, key, remote_service.to_data()) if remote_service.service_type() in datas: datas[remote_service.service_type()].append(remote_service) else: datas[remote_service.service_type()] = [remote_service] return datas else: # this is running on the client from Acquire.Client import Wallet as _Wallet wallet = _Wallet() return wallet.get_services()
def set_cluster(cluster, authorisation=None, passphrase=None, user=None): """Function used to set the single compute cluster that is connected to this compute service. This must be authorised by an admin user of this compute service """ if not isinstance(cluster, Cluster): raise TypeError("The cluster must be type Cluster") resource = "set_cluster %s" % cluster.fingerprint() from Acquire.Client import Authorisation as _Authorisation if Cluster._is_running_service(): from Acquire.Service import get_this_service as _get_this_service service = _get_this_service(need_private_access=True) if authorisation is not None: if not isinstance(authorisation, _Authorisation): raise TypeError( "The authorisation must be type Authorisation") service.assert_admin_authorised(authorisation, resource) else: # we are rotating keys, so check the passphrase against # the old passphrase cluster = Cluster.get_cluster() cluster.verify_passphrase(passphrase=passphrase, resource="set_cluster") from Acquire.ObjectStore import ObjectStore as _ObjectStore from Acquire.Service import get_service_account_bucket \ as _get_service_account_bucket bucket = _get_service_account_bucket() key = "compute/cluster" _ObjectStore.set_object_from_json(bucket, key, cluster.to_data()) else: authorisation = _Authorisation(user=user, resource=resource) compute_service = cluster.compute_service() args = {"authorisation": authorisation.to_data(), "cluster": cluster.to_data()} compute_service.call_function(function="set_cluster", args=args)
def _get_file_bucket(self, filekey=None): """Return the bucket that contains the file data for the file associated with 'filekey' in this drive """ from Acquire.ObjectStore import ObjectStore as _ObjectStore from Acquire.Service import get_service_account_bucket \ as _get_service_account_bucket from Acquire.Service import get_this_service as _get_this_service service = _get_this_service() bucket = _get_service_account_bucket() bucket_name = self._get_file_bucketname(filekey=filekey) try: return _ObjectStore.get_bucket(bucket=bucket, bucket_name=bucket_name, create_if_needed=True) except Exception as e: from Acquire.ObjectStore import RequestBucketError raise RequestBucketError("Unable to open the bucket '%s': %s" % (bucket_name, str(e)))
def _get_metadata_bucket(self): """Return the bucket that contains all of the metadata about the files for this drive """ from Acquire.ObjectStore import ObjectStore as _ObjectStore from Acquire.Service import get_service_account_bucket \ as _get_service_account_bucket from Acquire.Service import get_this_service as _get_this_service service = _get_this_service() bucket = _get_service_account_bucket() bucket_name = "user_metadata" try: return _ObjectStore.get_bucket(bucket=bucket, bucket_name=bucket_name, create_if_needed=True) except Exception as e: from Acquire.ObjectStore import RequestBucketError raise RequestBucketError("Unable to open the bucket '%s': %s" % (bucket_name, str(e)))
def accounting_service(self): """Return the accounting service that will honour this cheque. Note that this will only return the service if it is trusted by the service on which this function is called Returns: Service: Trusted accounting service """ from Acquire.Service import get_this_service as _get_this_service service = _get_this_service() accounting_service = service.get_trusted_service( self.accounting_service_url()) if not accounting_service.is_accounting_service(): from Acquire.Service import ServiceError raise ServiceError( "The service that is supposed to honour the cheque (%s) " "does not appear to be a valid accounting service" % (str(accounting_service))) return accounting_service
def challenge_service(self, service): """Send a challenge to the passed service, returning the actual service returned. This will only pass if our copy of the service matches us with the copy returned from the actual service. This verifies that there is a real service sitting at that URL, and that we have the right keys and certs """ from Acquire.Crypto import PrivateKey as _PrivateKey from Acquire.ObjectStore import bytes_to_string as _bytes_to_string from Acquire.Service import Service as _Service challenge = _PrivateKey.random_passphrase() pubkey = service.public_key() encrypted_challenge = pubkey.encrypt(challenge) args = {"challenge": _bytes_to_string(encrypted_challenge), "fingerprint": pubkey.fingerprint()} if service.uid().startswith("STAGE"): # we cannot call using the service directly, as it is # not yet fully constructed from Acquire.Service import get_this_service as _get_this_service from Acquire.Service import call_function as _call_function this_service = _get_this_service(need_private_access=True) result = _call_function(service_url=service.service_url(), function=None, args=args, args_key=service.public_key(), response_key=this_service.private_key(), public_cert=service.public_certificate()) else: result = service.call_function(function=None, args=args) if result["response"] != challenge: raise PermissionError( "Failure of the service %s to correctly respond " "to the challenge!" % service) return _Service.from_data(result["service_info"])
def create(username, password, _service_uid=None, _service_public_key=None): """Create a new account with username 'username', which will be secured using the passed password. Note that this will create an account with a specified user UID, meaning that different users can have the same username. We identify the right user via the combination of username, password and OTP code. Normally the UID of the service, and the skeleton key used to encrypt the backup password are obtained directly from the service. However, when initialising a new service we must pass these directly. In those cases, pass the object using _service_uid and _service_public_key This returns a tuple of the user_uid and OTP for the newly-created account """ from Acquire.ObjectStore import create_uuid as _create_uuid from Acquire.Crypto import PrivateKey as _PrivateKey from Acquire.Crypto import PublicKey as _PublicKey from Acquire.ObjectStore import ObjectStore as _ObjectStore from Acquire.Service import get_service_account_bucket \ as _get_service_account_bucket from Acquire.ObjectStore import bytes_to_string as _bytes_to_string from Acquire.Identity import UserCredentials as _UserCredentials from Acquire.ObjectStore import get_datetime_now_to_string \ as _get_datetime_now_to_string if _service_public_key is None: from Acquire.Service import get_this_service as _get_this_service service_pubkey = _get_this_service().public_skeleton_key() assert (service_pubkey is not None) else: service_pubkey = _service_public_key if not isinstance(service_pubkey, _PublicKey): raise TypeError("The service public key must be type PublicKey") if _service_uid is None: from Acquire.Service import get_this_service \ as _get_this_service service_uid = _get_this_service(need_private_access=False).uid() else: service_uid = _service_uid # create a UID for this new user user_uid = _create_uuid() # now create the primary password for this user and use # this to encrypt the special keys for this user privkey = _PrivateKey(name="user_secret_key %s %s" % (username, user_uid)) primary_password = _PrivateKey.random_passphrase() bucket = _get_service_account_bucket() # now create the credentials used to validate a login otp = _UserCredentials.create(user_uid=user_uid, password=password, primary_password=primary_password) # create the user account user = UserAccount(username=username, user_uid=user_uid, private_key=privkey, status="active") # now save a lookup from the username to this user_uid # (many users can have the same username). Use this lookup # to hold a recovery password for this account recovery_password = _bytes_to_string( service_pubkey.encrypt(primary_password)) key = "%s/names/%s/%s" % (_user_root, user.encoded_name(), user_uid) _ObjectStore.set_string_object(bucket=bucket, key=key, string_data=recovery_password) # now save a lookup from the hashed username+password # to the user_uid, so that we can # quickly find matching user_uids (expect few people will have # exactly the same username and password). This will # save the exact time this username-password combination # was set encoded_password = _UserCredentials.hash(username=username, password=password, service_uid=service_uid) key = "%s/passwords/%s/%s" % (_user_root, encoded_password, user_uid) _ObjectStore.set_string_object( bucket=bucket, key=key, string_data=_get_datetime_now_to_string()) # finally(!) save the account itself to the object store key = "%s/uids/%s" % (_user_root, user_uid) data = user.to_data(passphrase=primary_password) _ObjectStore.set_object_from_json(bucket=bucket, key=key, data=data) # return the OTP and user_uid return (user_uid, otp)
def login_root_url(self): """Return the root URL used to log into this account""" from Acquire.Service import get_this_service as _get_this_service return _get_this_service().canonical_url()
def __init__(self, url=None, key=None, encrypt_key=None, expires_datetime=None, is_readable=False, is_writeable=False, driver_details=None): """Construct an OSPar result by passing in the URL at which the object can be accessed, the UTC datetime when this expires, whether this is readable or writeable, and the encryption key to use to encrypt the OSPar. You can optionally pass in the key for the object in the object store that this provides access to. If this is not supplied, then an entire bucket is accessed). If 'is_readable', then read-access has been granted, while if 'is_writeable' then write access has been granted. Otherwise no access is possible. driver_details is provided by the machinery that creates the OSPar, and supplies extra details that are used by the driver to create, register and manage OSPars... You should not do anything with driver_details yourself """ service_url = None if url is None: is_readable = True self._uid = None else: from Acquire.Crypto import PublicKey as _PublicKey from Acquire.Crypto import PrivateKey as _PrivateKey if isinstance(encrypt_key, _PrivateKey): encrypt_key = encrypt_key.public_key() if not isinstance(encrypt_key, _PublicKey): raise TypeError( "You must supply a valid PublicKey to encrypt a OSPar") url = encrypt_key.encrypt(url) from Acquire.ObjectStore import create_uid as _create_uid self._uid = _create_uid() try: from Acquire.Service import get_this_service \ as _get_this_service service_url = _get_this_service().canonical_url() except: pass self._url = url self._key = key self._expires_datetime = expires_datetime self._service_url = service_url if driver_details is not None: if not isinstance(driver_details, dict): raise TypeError("The driver details must be a dictionary") self._driver_details = driver_details if is_readable: self._is_readable = True else: self._is_readable = False if is_writeable: self._is_writeable = True else: self._is_writeable = False if not (self._is_readable or self._is_writeable): from Acquire.Client import PARPermissionsError raise PARPermissionsError( "You cannot create a OSPar that has no read or write " "permissions!")
def read(self, spend, resource, receipt_by): """Read the cheque - this will read the cheque to return the decrypted contents. This will only work if this function is called on the accounting service that will cash the cheque, if the signature on the cheque matches the service that is authorised to cash the cheque, and if the passed resource matches the resource encoded in the cheque. If this is all correct, then the returned dictionary will contain; {"recipient_url": The URL of the service which was sent the cheque, "recipient_key_fingerprint": Verified fingerprint of the service key that signed this cheque "spend": The amount authorised by this cheque, "uid": The unique ID for this cheque, "resource": String that identifies the resource this cheque will be used to pay for, "account_uid": UID of the account from which funds will be drawn "authorisation" : Verified authorisation from the user who says they own the account for the spend "receipt_by" : Time when we must receipt the cheque, or we will lose the money } You must pass in the spend you want to draw from the cheque, a string representing the resource this cheque will be used to pay for, and the time by which you promise to receipt the cheque after cashing Args: spend (Decimal): Amount authorised by cheque resource (str): Resource to pay for receipt_by (datetime): Time cheque must be receipted by Returns: dict: Dictionary described above """ if self._cheque is None: raise PaymentError("You cannot read a null cheque") from Acquire.ObjectStore import string_to_decimal \ as _string_to_decimal from Acquire.ObjectStore import string_to_datetime \ as _string_to_datetime from Acquire.ObjectStore import datetime_to_string \ as _datetime_to_string from Acquire.Service import get_this_service as _get_this_service spend = _string_to_decimal(spend) resource = str(resource) receipt_by = _string_to_datetime(receipt_by) service = _get_this_service(need_private_access=True) # get the cheque data - this may have been signed try: cheque_data = _json.loads(self._cheque["signed_data"]) except: cheque_data = self._cheque # decrypt the cheque's data - only possible on the accounting service cheque_data = service.decrypt_data(cheque_data) # the date comprises the user-authorisation that acts as a # signature that the user wrote this cheque, and the info # for the cheque to say how it is valid from Acquire.Identity import Authorisation as _Authorisation auth = _Authorisation.from_data(cheque_data["authorisation"]) info = cheque_data["info"] # the json.dumps version is the resource used to verify # the above authorisation auth_resource = info # validate that the user authorised this cheque try: auth.verify(resource=info) except Exception as e: raise PaymentError( "The user's signature/authorisation for this cheque " "is not valid! ERROR: %s" % str(e)) info = _json.loads(info) # the user signed this cheque :-) info["authorisation"] = auth # check the signature if one was needed try: recipient_url = info["recipient_url"] except: recipient_url = None if recipient_url: # the recipient was specified - verify that we trust # the recipient, and that they have signed the cheque recipient_service = service.get_trusted_service( service_url=recipient_url) recipient_service.verify_data(self._cheque) info["recipient_key_fingerprint"] = self._cheque["fingerprint"] # validate that the item signature is correct try: cheque_resource = info["resource"] except: cheque_resource = None if cheque_resource is not None: if resource != resource: raise PaymentError( "Disagreement over the resource for which " "this cheque has been signed") info["resource"] = resource info["auth_resource"] = auth_resource try: max_spend = info["max_spend"] del info["max_spend"] except: max_spend = None if max_spend is not None: max_spend = _string_to_decimal(max_spend) if max_spend < spend: raise PaymentError( "The requested spend (%s) exceeds the authorised " "maximum value of the cheque" % (spend)) info["spend"] = spend try: expiry_date = info["expiry_date"] del expiry_date["expiry_date"] except: expiry_date = None if expiry_date is not None: expiry_date = _string_to_datetime(expiry_date) # validate that the cheque will not have expired # when we receipt it from Acquire.ObjectStore import get_datetime_now \ as _get_datetime_now now = _get_datetime_now() if now > receipt_by: raise PaymentError( "The time when you promised to receipt the cheque " "has already passed!") if receipt_by > expiry_date: raise PaymentError( "The cheque will have expired after you plan to " "receipt it!: %s versus %s" % (_datetime_to_string(receipt_by), _datetime_to_string(expiry_date))) info["receipt_by"] = receipt_by return info
def cash(self, spend, resource, receipt_within=3600): """Cash this cheque, specifying how much to be cashed, and the resource that will be paid for using this cheque. This will send the cheque to the accounting service (if we trust that accounting service). The accounting service will check that the cheque is valid, and the signature of the item is correct. It will then withdraw 'spend' funds from the account that signed the cheque, returning valid CreditNote(s) that can be trusted to show that the funds exist. If 'receipt_within' is set, then the CreditNotes will be automatically cancelled if they are not receipted within 'receipt_within' seconds It is your responsibility to receipt the note for the actual valid incurred once the service has been delivered, thereby actually transferring the cheque funds into your account (on that accounting service) This returns a list of the CreditNote(s) that were cashed from the cheque Args: spend (Decimal): Value to withdraw resource (str): Resource to spend value on receipt_within (datetime, default=3600): Time to receipt the cashing of this cheque by Returns: list: List of CreditNotes """ if self._cheque is None: raise PaymentError("You cannot cash a null cheque!") # sign the cheque to show we have seen it from Acquire.Service import get_this_service as _get_this_service service = _get_this_service(need_private_access=True) self._cheque = service.sign_data(self._cheque) # get the trusted accounting service that will honour the cheque accounting_service = self.accounting_service() # when do we guarantee to receipt the credit notes by? from Acquire.ObjectStore import get_datetime_future \ as _get_datetime_future receipt_by = _get_datetime_future(receipt_within) # which account should the money be paid into? account_uid = service.service_user_account_uid( accounting_service=accounting_service) # next - send the cheque to the accounting service to # show that we know the item_id and want to cash it from Acquire.ObjectStore import decimal_to_string \ as _decimal_to_string from Acquire.ObjectStore import datetime_to_string \ as _datetime_to_string from Acquire.ObjectStore import string_to_list \ as _string_to_list result = accounting_service.call_function( function="cash_cheque", args={"cheque": self.to_data(), "spend": _decimal_to_string(spend), "resource": str(resource), "account_uid": account_uid, "receipt_by": _datetime_to_string(receipt_by)}) credit_notes = None try: from Acquire.Accounting import CreditNote as _CreditNote credit_notes = _string_to_list(result["credit_notes"], _CreditNote) except Exception as e: raise PaymentError( "Attempt to cash the cheque has not resulted in a " "valid CreditNote? Error = %s" % str(e)) total_cashed = 0 for note in credit_notes: total_cashed = total_cashed + note.value() if note.account_uid() != account_uid: raise PaymentError( "The cashed cheque is paying into the wrong account! " "%s. It should be going to %s" % (note.account_uid(), account_uid)) if total_cashed != spend: raise PaymentError( "The value of the cheque (%s) does not match the total value " "of the credit note(s) returned (%s)" % (spend, total_cashed)) return credit_notes
def get_service(self, service_uid=None, service_url=None): """Load and return the service with specified url or uid from the registry. This will consult with other registry services to find the matching service """ from Acquire.ObjectStore import ObjectStore as _ObjectStore from Acquire.Service import Service as _Service from Acquire.ObjectStore import string_to_encoded \ as _string_to_encoded from Acquire.Service import get_this_service as _get_this_service this_service = _get_this_service(need_private_access=False) if service_url is not None: from Acquire.Service import Service as _Service service_url = _Service.get_canonical_url(service_url) if this_service.uid() == service_uid: return this_service elif this_service.canonical_url() == service_url: return this_service bucket = self.get_bucket() service_key = self.get_service_key(service_uid=service_uid, service_url=service_url) service = None if service_key is not None: try: data = _ObjectStore.get_object_from_json(bucket=bucket, key=service_key) service = _Service.from_data(data) except: pass if service is not None: must_write = False if service.uid() == "STAGE1": # we need to directly ask the service for its info service = self.challenge_service(service) if service.uid() == "STAGE1": from Acquire.Service import MissingServiceError raise MissingServiceError( "Service %s|%s not available as it is still under " "construction!" % (service_uid, service)) # we can now move this service from pending to active uidkey = self._get_key_for_uid(service.uid()) domain = self._get_domain(service.service_url()) domainroot = self._get_root_key_for_domain(domain=domain) pending_key = "%s/pending/%s" % (domainroot, service.uid()) active_key = "%s/active/%s" % (domainroot, service.uid()) try: _ObjectStore.delete_object(bucket=bucket, key=pending_key) except: pass try: _ObjectStore.set_string_object(bucket=bucket, key=active_key, string_data=uidkey) except: pass must_write = True elif service.should_refresh_keys(): service.refresh_keys() must_write = True if must_write: data = service.to_data() _ObjectStore.set_object_from_json(bucket=bucket, key=service_key, data=data) return service # we only get here if we can't find the service on this registry. # In the future, we will use the initial part of the UID of # the service to ask its registering registry for its data. # For now, we just raise an error from Acquire.Service import MissingServiceError raise MissingServiceError( "No service available: service_url=%s service_uid=%s" % (service_url, service_uid))
def call_function(service_url, function=None, args=None, args_key=None, response_key=None, public_cert=None): """Call the remote function called 'function' at 'service_url' passing in named function arguments in 'kwargs'. If 'args_key' is supplied, then encrypt the arguments using 'args'. If 'response_key' is supplied, then tell the remote server to encrypt the response using the public version of 'response_key', so that we can decrypt it in the response. If 'public_cert' is supplied then we will ask the service to sign their response using their service signing certificate, and we will validate the signature using 'public_cert' """ if args is None: args = {} from Acquire.Service import is_running_service as _is_running_service from Acquire.ObjectStore import get_datetime_now_to_string \ as _get_datetime_now_to_string service = None if _is_running_service(): from Acquire.Service import get_this_service as _get_this_service try: service = _get_this_service(need_private_access=False) except: pass if service is not None: if service.canonical_url() == service_url: result = service._call_local_function(function=function, args=args) return unpack_return_value(return_value=result) response_key = _get_key(response_key) if response_key: args_json = pack_arguments(function=function, args=args, key=args_key, response_key=response_key.public_key(), public_cert=public_cert) else: args_json = pack_arguments(function=function, args=args, key=args_key) response = None try: from Acquire.Stubs import requests as _requests response = _requests.post(service_url, data=args_json, timeout=60.0) except Exception as e: from Acquire.Service import RemoteFunctionCallError raise RemoteFunctionCallError( "Cannot call remote function '%s' at '%s' because of a possible " "network issue: requests exeption = '%s'" % (function, service_url, str(e))) args = None args_json = None args_key = None if response.status_code != 200: from Acquire.Service import RemoteFunctionCallError raise RemoteFunctionCallError( "Cannot call remote function '%s' as '%s'. Invalid error code " "%d returned. Message:\n%s" % (function, service_url, response.status_code, str( response.content))) if response.encoding == "utf-8" or response.encoding is None: result = response.content.decode("utf-8") else: from Acquire.Service import RemoteFunctionCallError raise RemoteFunctionCallError( "Cannot call remote function '%s' as '%s'. Invalid data encoding " "%s returned. Message:\n%s" % (function, service_url, response.encoding, str(response.content))) return unpack_return_value(return_value=result, key=response_key, public_cert=public_cert, function=function, service=service_url)
def get_trusted_service(service_url=None, service_uid=None, service_type=None, autofetch=True): """Return the trusted service info for the service with specified service_url or service_uid""" if service_url is not None: from Acquire.Service import Service as _Service service_url = _Service.get_canonical_url(service_url, service_type=service_type) from Acquire.Service import is_running_service as _is_running_service if _is_running_service(): from Acquire.Service import get_this_service as _get_this_service from Acquire.Service import Service as _Service from Acquire.Service import get_service_account_bucket as \ _get_service_account_bucket from Acquire.ObjectStore import ObjectStore as _ObjectStore from Acquire.ObjectStore import url_to_encoded as \ _url_to_encoded service = _get_this_service() if service_url is not None and service.canonical_url() == service_url: # we trust ourselves :-) return service if service_uid is not None and service.uid() == service_uid: # we trust ourselves :-) return service bucket = _get_service_account_bucket() uidkey = None data = None if service_uid is not None: uidkey = "_trusted/uid/%s" % service_uid try: data = _ObjectStore.get_object_from_json(bucket, uidkey) except: pass elif service_url is not None: urlkey = "_trusted/url/%s" % _url_to_encoded(service_url) try: uidkey = _ObjectStore.get_string_object(bucket, urlkey) if uidkey is not None: data = _ObjectStore.get_object_from_json(bucket, uidkey) except: pass if data is not None: remote_service = _Service.from_data(data) if remote_service.should_refresh_keys(): # need to update the keys in our copy of the service remote_service.refresh_keys() if uidkey is not None: _ObjectStore.set_object_from_json(bucket, uidkey, remote_service.to_data()) return remote_service if not autofetch: from Acquire.Service import ServiceAccountError if service_uid is not None: raise ServiceAccountError( "We do not trust the service with UID '%s'" % service_uid) else: raise ServiceAccountError( "We do not trust the service at URL '%s'" % service_url) # we can try to fetch this data - we will ask our own # registry from Acquire.Registry import get_trusted_registry_service \ as _get_trusted_registry_service registry = _get_trusted_registry_service(service_uid=service.uid()) service = registry.get_service(service_uid=service_uid, service_url=service_url) from Acquire.Service import trust_service as _trust_service _trust_service(service) return service else: # this is running on the client from Acquire.Client import Wallet as _Wallet wallet = _Wallet() service = wallet.get_service(service_uid=service_uid, service_url=service_url, service_type=service_type, autofetch=autofetch) return service
def execute(self, cheque): """Execute (start) this work, using the passed cheque for payment. Note that you can't perform the same work twice """ if self.is_null(): from Acquire.Accounting import PaymentError raise PaymentError("You cannot try to execute null work!") from Acquire.Client import Cheque as _Cheque if not isinstance(cheque, _Cheque): raise TypeError("You must pass a valid Cheque as payment " "for the work") if self._credit_notes is not None: raise PermissionError("You cannot start a piece of work twice!") from Acquire.Service import get_this_service as _get_this_service access_service = _get_this_service(need_private_access=True) compute_service = self.compute_service() storage_service = self.storage_service() accounting_service = cheque.accounting_service() access_user = access_service.login_service_user() account_uid = access_service.service_user_account_uid( accounting_service=accounting_service) from Acquire.Client import Account as _Account access_account = _Account(user=access_user, account_uid=account_uid, accounting_service=accounting_service) # TODO - validate that the cost of the work on the compute # and storage services is covered by the passed cheque try: credit_notes = cheque.cash(spend=self.total_cost(), resource=self.request().fingerprint()) except Exception as e: from Acquire.Service import exception_to_string from Acquire.Accounting import PaymentError raise PaymentError( "Problem cashing the cheque used to pay for the calculation: " "\n\nCAUSE: %s" % exception_to_string(e)) if credit_notes is None or len(credit_notes) == 0: from Acquire.Accounting import PaymentError raise PaymentError("Cannot be paid!") # make sure that we have been paid! for credit_note in credit_notes: if credit_note.credit_account_uid() != access_account.uid(): raise PaymentError("The wrong account has been paid!?!") self._status = "awaiting (paid)" self._credit_notes = credit_notes # work out when this job MUST have finished. If the job # has not completed before this time then it will be killed from Acquire.ObjectStore import get_datetime_future \ as _get_datetime_future endtime = _get_datetime_future(days=2) # this should be calculated # save the WorkSheet to the object store so we don't lose the # value in the credit notes self.save() compute_cheque = _Cheque.write( account=access_account, resource="work %s" % self.uid(), max_spend=10.0, recipient_url=compute_service.canonical_url(), expiry_date=endtime) storage_cheque = _Cheque.write( account=access_account, resource="work %s" % self.uid(), max_spend=10.0, recipient_url=storage_service.canonical_url(), expiry_date=endtime) self._compute_cheque = compute_cheque self._storage_cheque = storage_cheque # now create a Drive on the storage service that will hold # the output for this job from Acquire.Client import Drive as _Drive from Acquire.Client import StorageCreds as _StorageCreds from Acquire.Client import ACLRule as _ACLRule from Acquire.Client import ACLRules as _ACLRules from Acquire.Client import ACLUserRules as _ACLUserRules creds = _StorageCreds(user=access_user, storage_service=storage_service) rule = _ACLUserRules.owner(user_guid=access_user.guid()).add( user_guid=self.user_guid(), rule=_ACLRule.reader()) aclrules = _ACLRules(rule=rule, default_rule=_ACLRule.denied()) output_drive = _Drive(name="output_%s" % self.uid(), creds=creds, aclrules=aclrules, cheque=storage_cheque, max_size="10MB", autocreate=True) self._output_loc = output_drive.metadata().location() self._status = "awaiting (paid, have drive)" self.save() from Acquire.Client import PAR as _PAR par = _PAR(location=self._output_loc, user=access_user, aclrule=_ACLRule.writer(), expires_datetime=endtime) secret = compute_service.encrypt_data(par.secret()) args = { "worksheet_uid": self.uid(), "request": self.request().to_data(), "par": par.to_data(), "secret": secret, "cheque": compute_cheque.to_data() } self._status = "submitting" self.save() response = compute_service.call_function(function="submit_job", args=args) print(response) self._status = "submitted" self.save() # the service user will log out automatically on destruction, but # let us make sure! access_user.logout()