def post(self, namespace=None, key_name=None, key=None): if not namespace: return api_base.error(400, 'no namespace specified') with db.get_lock('namespace', None, 'all', op='Namespace update'): rec = db.get_namespace(namespace) if not rec: rec = {'name': namespace, 'keys': {}} # Allow shortcut of creating key at same time as the namespace if key_name: if not key: return api_base.error(400, 'no key specified') if not isinstance(key, str): # Must be a string to encode() return api_base.error(400, 'key is not a string') if key_name == 'service_key': return api_base.error(403, 'illegal key name') encoded = str( base64.b64encode( bcrypt.hashpw(key.encode('utf-8'), bcrypt.gensalt())), 'utf-8') rec['keys'][key_name] = encoded # Initialise metadata db.persist_metadata('namespace', namespace, {}) db.persist_namespace(namespace, rec) return namespace
def post(self, namespace=None, key=None): if not namespace: return api_base.error(400, 'missing namespace in request') if not key: return api_base.error(400, 'missing key in request') if not isinstance(key, str): # Must be a string to encode() return api_base.error(400, 'key is not a string') ns = db.get_namespace(namespace) if not ns: return api_base.error(401, 'unauthorized') service_key = ns.get('service_key') if service_key and key == service_key: return { 'access_token': create_access_token(identity=[namespace, '_service_key']) } for key_name in ns.get('keys', {}): possible_key = base64.b64decode(ns['keys'][key_name]) if bcrypt.checkpw(key.encode('utf-8'), possible_key): return { 'access_token': create_access_token(identity=[namespace, key_name]) } return api_base.error(401, 'unauthorized')
def get(self, blob_uuid=None, offset=0): # Ensure the blob exists b = Blob.from_db(blob_uuid) if not b: return api_base.error(404, 'blob not found') # Fast path if we have the blob locally os.makedirs(os.path.join(config.STORAGE_PATH, 'blobs'), exist_ok=True) blob_path = os.path.join(config.STORAGE_PATH, 'blobs', blob_uuid) if os.path.exists(blob_path): return flask.Response(flask.stream_with_context( _read_file(blob_path, offset)), mimetype='text/plain', status=200) # Otherwise find a node which has the blob and proxy. locations = b.locations if not locations: return api_base.error(404, 'blob missing') random.shuffle(locations) return flask.Response(flask.stream_with_context( _read_remote(locations[0], blob_uuid, offset=offset)), mimetype='text/plain', status=200)
def get(self, label_name=None): artifacts = list( Artifacts(filters=[ partial(type_filter, Artifact.TYPE_LABEL), partial(url_filter, _label_url(label_name)), active_states_filter ])) if len(artifacts) == 0: api_base.error(404, 'label %s not found' % label_name) return artifacts[0].external_view()
def delete(self, namespace=None, key=None, value=None): if not key: return api_base.error(400, 'no key specified') with db.get_lock('metadata', 'namespace', namespace, op='Metadata delete'): md = db.get_metadata('namespace', namespace) if md is None or key not in md: return api_base.error(404, 'key not found') del md[key] db.persist_metadata('namespace', namespace, md)
def post(self, label_name=None, blob_uuid=None, max_versions=0): b = Blob.from_db(blob_uuid) if not b: return api_base.error(404, 'blob not found') try: b.ref_count_inc() except BlobDeleted: return api_base.error(400, 'blob has been deleted') a = Artifact.from_url(Artifact.TYPE_LABEL, _label_url(label_name), max_versions) a.add_index(blob_uuid) a.state = dbo.STATE_CREATED return a.external_view()
def metadata_putpost(meta_type, owner, key, value): if meta_type not in ['namespace', 'instance', 'network']: return api_base.error(500, 'invalid meta_type %s' % meta_type) if not key: return api_base.error(400, 'no key specified') if not value: return api_base.error(400, 'no value specified') with db.get_lock('metadata', meta_type, owner, op='Metadata update'): md = db.get_metadata(meta_type, owner) if md is None: md = {} md[key] = value db.persist_metadata(meta_type, owner, md)
def delete(self, label_name=None): artifacts = list( Artifacts(filters=[ partial(type_filter, Artifact.TYPE_LABEL), partial(url_filter, _label_url(label_name)), active_states_filter ])) if len(artifacts) == 0: api_base.error(404, 'label %s not found' % label_name) for a in artifacts: a.state = dbo.STATE_DELETED for blob_index in a.get_all_indexes(): b = Blob.from_db(blob_index['blob_uuid']) b.ref_count_dec()
def delete(self, artifact_uuid=None, artifact_from_db=None, version_id=0): try: ver_index = int(version_id) except ValueError: return api_base.error(400, 'version index is not an integer') indexes = list(artifact_from_db.get_all_indexes()) for idx in indexes: if idx['index'] == ver_index: artifact_from_db.del_index(idx['index']) if len(indexes) == 1: artifact_from_db.state = Artifact.STATE_DELETED return return api_base.error(404, 'artifact index not found')
def delete(self, namespace, key_name): if not namespace: return api_base.error(400, 'no namespace specified') if not key_name: return api_base.error(400, 'no key name specified') with db.get_lock('namespace', None, namespace, op='Namespace key delete'): ns = db.get_namespace(namespace) if ns.get('keys') and key_name in ns['keys']: del ns['keys'][key_name] else: return api_base.error(404, 'key name not found in namespace') db.persist_namespace(namespace, ns)
def post(self, artifact_name=None, upload_uuid=None, source_url=None): u = Upload.from_db(upload_uuid) if not u: return api_base.error(404, 'upload not found') if u.node != config.NODE_NAME: url = 'http://%s:%d%s' % (u.node, config.API_PORT, flask.request.environ['PATH_INFO']) api_token = util_general.get_api_token( 'http://%s:%d' % (u.node, config.API_PORT), namespace=get_jwt_identity()[0]) r = requests.request( flask.request.environ['REQUEST_METHOD'], url, data=json.dumps(api_base.flask_get_post_body()), headers={ 'Authorization': api_token, 'User-Agent': util_general.get_user_agent() }) LOG.info('Proxied %s %s returns: %d, %s' % (flask.request.environ['REQUEST_METHOD'], url, r.status_code, r.text)) resp = flask.Response(r.text, mimetype='application/json') resp.status_code = r.status_code return resp if not source_url: source_url = ('%s%s/%s' % (UPLOAD_URL, get_jwt_identity()[0], artifact_name)) a = Artifact.from_url(Artifact.TYPE_IMAGE, source_url) with a.get_lock(ttl=(12 * constants.LOCK_REFRESH_SECONDS), timeout=config.MAX_IMAGE_TRANSFER_SECONDS): blob_uuid = str(uuid.uuid4()) blob_dir = os.path.join(config.STORAGE_PATH, 'blobs') blob_path = os.path.join(blob_dir, blob_uuid) upload_dir = os.path.join(config.STORAGE_PATH, 'uploads') upload_path = os.path.join(upload_dir, u.uuid) # NOTE(mikal): we can't use os.rename() here because these paths # might be on different filesystems. shutil.move(upload_path, blob_path) st = os.stat(blob_path) b = Blob.new( blob_uuid, st.st_size, time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime()), time.time()) b.state = Blob.STATE_CREATED b.ref_count_inc() b.observe() b.request_replication() a.state = Artifact.STATE_CREATED a.add_event('upload', None, None, 'success') a.add_index(b.uuid) a.state = Artifact.STATE_CREATED return a.external_view()
def post(self, artifact_uuid=None, artifact_from_db=None, max_versions=config.ARTIFACT_MAX_VERSIONS_DEFAULT): try: mv = int(max_versions) except ValueError: return api_base.error(400, 'max version is not an integer') artifact_from_db.max_versions = mv
def wrapper(*args, **kwargs): if 'artifact_uuid' in kwargs: kwargs['artifact_from_db'] = Artifact.from_db( kwargs['artifact_uuid']) if not kwargs.get('artifact_from_db'): LOG.with_field('artifact', kwargs['artifact_uuid']).info('Artifact not found') return api_base.error(404, 'artifact not found') return func(*args, **kwargs)
def post(self, interface_uuid=None): ni, n, err = api_util.safe_get_network_interface(interface_uuid) if err: return err float_net = net.Network.from_db('floating') if not float_net: return api_base.error(404, 'floating network not found') # Address is freed as part of the job, so code is "unbalanced" compared # to above for reasons. etcd.enqueue('networknode', DefloatNetworkInterfaceTask(n.uuid, interface_uuid))
def assign_floating_ip(ni): float_net = net.Network.from_db('floating') if not float_net: return api_base.error(404, 'floating network not found') # Address is allocated and added to the record here, so the job has it later. db.add_event('interface', ni.uuid, 'api', 'float', None, None) with db.get_lock('ipmanager', None, 'floating', ttl=120, op='Interface float'): ipm = IPManager.from_db('floating') addr = ipm.get_random_free_address(ni.unique_label()) ipm.persist() ni.floating = addr
def safe_get_network_interface(interface_uuid): ni = NetworkInterface.from_db(interface_uuid) if not ni: return None, None, api_base.error(404, 'interface not found') log = LOG.with_fields({'network': ni.network_uuid, 'networkinterface': ni.uuid}) n = net.Network.from_db(ni.network_uuid) if not n: log.info('Network not found or deleted') return None, None, api_base.error(404, 'interface network not found') if get_jwt_identity()[0] not in [n.namespace, 'system']: log.info('Interface not found, failed ownership test') return None, None, api_base.error(404, 'interface not found') i = Instance.from_db(ni.instance_uuid) if get_jwt_identity()[0] not in [i.namespace, 'system']: log.with_object(i).info('Instance not found, failed ownership test') return None, None, api_base.error(404, 'interface not found') return ni, n, None
def _namespace_keys_putpost(namespace=None, key_name=None, key=None): if not namespace: return api_base.error(400, 'no namespace specified') if not key_name: return api_base.error(400, 'no key name specified') if not key: return api_base.error(400, 'no key specified') if key_name == 'service_key': return api_base.error(403, 'illegal key name') with db.get_lock('namespace', None, 'all', op='Namespace key update'): rec = db.get_namespace(namespace) if not rec: return api_base.error(404, 'namespace does not exist') encoded = str( base64.b64encode( bcrypt.hashpw(key.encode('utf-8'), bcrypt.gensalt())), 'utf-8') rec['keys'][key_name] = encoded db.persist_namespace(namespace, rec) return key_name
def delete(self, namespace): if not namespace: return api_base.error(400, 'no namespace specified') if namespace == 'system': return api_base.error(403, 'you cannot delete the system namespace') # The namespace must be empty instances = [] deleted_instances = [] for i in instance.instances_in_namespace(namespace): if i.state.value in [dbo.STATE_DELETED, dbo.STATE_ERROR]: deleted_instances.append(i.uuid) else: LOG.withFields({ 'instance': i.uuid, 'state': i.state }).info('Blocks namespace delete') instances.append(i.uuid) if len(instances) > 0: return api_base.error( 400, 'you cannot delete a namespace with instances') networks = [] for n in net.networks_in_namespace(namespace): if not n.is_dead(): LOG.withFields({ 'network': n.uuid, 'state': n.state }).info('Blocks namespace delete') networks.append(n.uuid) if len(networks) > 0: return api_base.error( 400, 'you cannot delete a namespace with networks') db.delete_namespace(namespace) db.delete_metadata('namespace', namespace)
def delete(self, artifact_uuid=None, artifact_from_db=None): """Delete an artifact from the cluster Artifacts can only be deleted from the system if they are not in use. The actual deletion of the on-disk files is left to the cleaner daemon. It is acknowledged that there is a potential race condition between the check that an artifact is not in use and the marking of the artifact as deleted. This is only caused by a user simultaneously deleting an artifact and attempting to start a VM using it. It is recommended that the user does not do that. """ # TODO(andy): Enforce namespace permissions when snapshots have namespaces # TODO(mikal): this should all be refactored to be in the object if artifact_from_db.state.value == Artifact.STATE_DELETED: # Already deleted, nothing to do. return # Check for instances using a blob referenced by the artifact. blobs = [] sole_ref_in_use = [] for blob_index in artifact_from_db.get_all_indexes(): b = Blob.from_db(blob_index['blob_uuid']) if b: blobs.append(b) if b.ref_count == 1: sole_ref_in_use += b.instances if sole_ref_in_use: return api_base.error( 400, 'Cannot delete last reference to blob in use by instance (%s)' % (', '.join(sole_ref_in_use), )) artifact_from_db.delete() for b in blobs: b.ref_count_dec()
def put(self, namespace=None, key_name=None, key=None): rec = db.get_namespace(namespace) if key_name not in rec['keys']: return api_base.error(404, 'key does not exist') return _namespace_keys_putpost(namespace, key_name, key)