def test_tickets_extend_no_ticket_id(fake_time): ticket = testutils.create_ticket() tickets.add(ticket) prev_ticket = tickets.get(ticket["uuid"]).info() body = json.dumps({"timeout": 300}) res = http.unix_request(config.tickets.socket, "PATCH", "/tickets/", body) cur_ticket = tickets.get(ticket["uuid"]).info() assert res.status == 400 assert cur_ticket == prev_ticket
def test_tickets_extend_invalid_json(fake_time): ticket = testutils.create_ticket() tickets.add(ticket) prev_ticket = tickets.get(ticket["uuid"]).info() res = http.unix_request(config.tickets.socket, "PATCH", "/tickets/%(uuid)s" % ticket, "{invalid}") cur_ticket = tickets.get(ticket["uuid"]).info() assert res.status == 400 assert cur_ticket == prev_ticket
def test_tickets_idle_time_inactive(fake_time): ticket = testutils.create_ticket() tickets.add(ticket) # Ticket idle time starts with ticket is added. assert tickets.get(ticket["uuid"]).idle_time == 0 # Simulate time passing without any request. fake_time.now += 200 assert tickets.get(ticket["uuid"]).idle_time == 200
def test_tickets_extend_expired_ticket(fake_time): ticket = testutils.create_ticket() tickets.add(ticket) # Make the ticket expire. fake_time.now += 500 server_ticket = tickets.get(ticket["uuid"]).info() # Extend the expired ticket. body = json.dumps({"timeout": 300}) res = http.unix_request(config.tickets.socket, "PATCH", "/tickets/%(uuid)s" % ticket, body) assert res.status == 200 server_ticket = tickets.get(ticket["uuid"]).info() assert server_ticket["expires"] == 800
def test_images_options_ticket_expired(fake_time): ticket = testutils.create_ticket(timeout=300) tickets.add(ticket) server_ticket = tickets.get(ticket["uuid"]).info() assert server_ticket["expires"] == 300 # Make the ticket expire fake_time.now += 300 res = http.options("/images/" + ticket["uuid"]) assert res.status == 403 server_ticket = tickets.get(ticket["uuid"]).info() assert server_ticket["expires"] == 300
def test_images_options_extends_ticket(fake_time): ticket = testutils.create_ticket() tickets.add(ticket) server_ticket = tickets.get(ticket["uuid"]).info() assert server_ticket["expires"] == 300 fake_time.now += 200 res = http.options("/images/" + ticket["uuid"]) assert res.status == 200 res.read() server_ticket = tickets.get(ticket["uuid"]).info() assert server_ticket["expires"] == 500
def test_images_upload_extends_ticket(tmpdir, fake_time): image = testutils.create_tempfile(tmpdir, "image", "before") ticket = testutils.create_ticket(url="file://" + str(image)) tickets.add(ticket) server_ticket = tickets.get(ticket["uuid"]).info() assert server_ticket["expires"] == 300 fake_time.now += 200 res = http.put("/images/" + ticket["uuid"], "") assert res.status == 200 res.read() server_ticket = tickets.get(ticket["uuid"]).info() assert server_ticket["expires"] == 500
def test_images_download_extends_ticket(tmpdir, fake_time): size = 1024 image = testutils.create_tempfile(tmpdir, "image", size=size) ticket = testutils.create_ticket(url="file://" + str(image), size=size) tickets.add(ticket) server_ticket = tickets.get(ticket["uuid"]).info() assert server_ticket["expires"] == 300 fake_time.now += 200 res = http.get("/images/" + ticket["uuid"]) assert res.status == 200 res.read() server_ticket = tickets.get(ticket["uuid"]).info() assert server_ticket["expires"] == 500
def test_download_progress(tmpdir): size = 1024**2 * 50 filename = tmpdir.join("image") with open(str(filename), 'wb') as image: image.truncate(size) ticket = testutils.create_ticket(url="file://" + str(filename), ops=["read"], size=size) add_ticket(ticket) ticket = tickets.get(ticket["uuid"]) # No operations assert not ticket.active() assert ticket.transferred() == 0 res = download(ticket.uuid) res.read(1024**2) # The server has sent some chunks assert ticket.active() assert 0 < ticket.transferred() < size res.read() # The server has sent all the chunks - download completed assert not ticket.active() assert ticket.transferred() == size
def test_download_progress(tmpdir): size = 1024**2 * 10 filename = tmpdir.join("image") with open(str(filename), 'wb') as image: image.truncate(size) ticket = testutils.create_ticket(url="file://" + str(filename), ops=["read"], size=size) tickets.add(ticket) ticket = tickets.get(ticket["uuid"]) # No operations assert not ticket.active() assert ticket.transferred() == 0 res = http.get("/images/" + ticket.uuid) res.read(1024**2) # The server has sent some chunks assert ticket.active() assert 0 < ticket.transferred() < size res.read() # The server has sent all the chunks but we need to give it time to # touch the ticket. time.sleep(0.2) assert not ticket.active() assert ticket.transferred() == size
def test_images_flush_extends_ticket(tmpdir, fake_time): data = "x" * 512 image = testutils.create_tempfile(tmpdir, "image", data) ticket = testutils.create_ticket(url="file://" + str(image)) tickets.add(ticket) server_ticket = tickets.get(ticket["uuid"]).info() assert server_ticket["expires"] == 300 fake_time.now += 200 body = json.dumps({"op": "flush"}).encode("ascii") res = http.patch("/images/" + ticket["uuid"], body) assert res.status == 200 res.read() server_ticket = tickets.get(ticket["uuid"]).info() assert server_ticket["expires"] == 500
def test_tickets_idle_time_options(fake_time): ticket = testutils.create_ticket(url="file:///no/such/file") tickets.add(ticket) # Request must reset idle time. fake_time.now += 200 http.options("/images/" + ticket["uuid"]) assert tickets.get(ticket["uuid"]).idle_time == 0
def test_tickets_idle_time_get(fake_time, tmpdir): image = testutils.create_tempfile(tmpdir, "image", "a" * 8192) ticket = testutils.create_ticket(url="file://" + str(image)) tickets.add(ticket) # Request must reset idle time. fake_time.now += 200 http.get("/images/" + ticket["uuid"]) assert tickets.get(ticket["uuid"]).idle_time == 0
def test_tickets_idle_time_patch(fake_time, tmpdir, msg): image = testutils.create_tempfile(tmpdir, "image", "a" * 8192) ticket = testutils.create_ticket(url="file://" + str(image)) tickets.add(ticket) # Request must reset idle time. fake_time.now += 200 body = json.dumps(msg).encode('ascii') http.patch("/images/" + ticket["uuid"], body, headers={"content-type": "application/json"}) assert tickets.get(ticket["uuid"]).idle_time == 0
def test_tickets_put(fake_time): ticket = testutils.create_ticket(sparse=False) body = json.dumps(ticket) res = http.unix_request(config.tickets.socket, "PUT", "/tickets/%(uuid)s" % ticket, body) # Server adds expires key ticket["expires"] = int(util.monotonic_time()) + ticket["timeout"] ticket["active"] = False ticket["idle_time"] = 0 server_ticket = tickets.get(ticket["uuid"]).info() assert res.status == 200 assert res.getheader("content-length") == "0" assert server_ticket == ticket
def test_tickets_extend(fake_time): ticket = testutils.create_ticket(sparse=False) tickets.add(ticket) patch = {"timeout": 300} body = json.dumps(patch) fake_time.now += 240 res = http.unix_request(config.tickets.socket, "PATCH", "/tickets/%(uuid)s" % ticket, body) ticket["expires"] = int(fake_time.now + ticket["timeout"]) ticket["active"] = False ticket["idle_time"] = 240 server_ticket = tickets.get(ticket["uuid"]).info() assert res.status == 200 assert res.getheader("content-length") == "0" assert server_ticket == ticket
def test_tickets_idle_time_active(fake_time, tmpdir): filename = tmpdir.join("image") # Note: must be big enough so the request remain active. size = 1024**2 * 10 with open(str(filename), 'wb') as image: image.truncate(size) ticket = testutils.create_ticket(url="file://" + str(filename), ops=["read"], size=size) tickets.add(ticket) # Start a download, but read only 1 byte to make sure the operation becomes # active but do not complete. res = http.get("/images/" + ticket["uuid"]) res.read(1) # Active ticket idle time is always 0. fake_time.now += 200 assert tickets.get(ticket["uuid"]).idle_time == 0
def get_ticket(uuid): # Get a copy of the current server ticket, simulating a get request ticket = tickets.get(uuid) ticket = json.loads(json.dumps(ticket)) ticket["url"] = urllib_parse.urlunparse(ticket["url"]) return ticket
def post(self, ticket_id): if not ticket_id: raise HTTPBadRequest("Ticket id is required") body = self.request.body methodargs = json.loads(body) if not 'backup_path' in methodargs: raise HTTPBadRequest( "Malformed request. Requires backup_path in the body") destdir = os.path.split(methodargs['backup_path'])[0] if not os.path.exists(destdir): raise HTTPBadRequest("Backup_path does not exists") with open(os.path.join(CONF_DIR, "daemon.conf")) as f: sample_config = f.read() config = ConfigParser.RawConfigParser(allow_no_value=True) config.readfp(io.BytesIO(sample_config)) nfsshare = config.get('nfs_config', 'nfs_share') mountpath = config.get('nfs_config', 'mount_path') if not nfs_mount.is_mounted(nfsshare, mountpath): if not nfs_mount.mount_backup_target(nfsshare, mountpath): raise HTTPBadRequest("Backup target not mounted.") # TODO: cancel copy if ticket expired or revoked if methodargs['method'] == 'backup': offset = 0 size = None if self.request.range: offset = self.request.range.start if self.request.range.end is not None: size = self.request.range.end - offset ticket = tickets.authorize(ticket_id, "read", 0, size) self.log.debug("disk %s to %s for ticket %s", body, ticket.url.path, ticket_id) try: ctask = celery_tasks.backup.apply_async( (ticket_id, ticket.url.path, methodargs['backup_path'], tickets.get(ticket_id).size, methodargs['type'], self.config.daemon.buffer_size, methodargs['recent_snap_id']), #queue='backup_tasks', retry=True, retry_policy={ 'max_retries': 3, 'interval_start': 3, 'interval_step': 0.5, 'interval_max': 0.5, }) except celery_tasks.backup.OperationalError as exc: self.log.info("Submitting celery task raised: %r", exc) print "Submitted backup" elif methodargs['method'] == 'restore': size = self.request.content_length if size is None: raise HTTPBadRequest("Content-Length header is required") if size < 0: raise HTTPBadRequest("Invalid Content-Length header: %r" % size) content_range = web.content_range(self.request) offset = content_range.start or 0 ticket = tickets.authorize(ticket_id, "write", offset, size) self.log.debug("disk %s to %s for ticket %s", body, ticket.url.path, ticket_id) try: ctask = celery_tasks.restore.apply_async( (ticket_id, ticket.url.path, methodargs['backup_path'], tickets.get(ticket_id).size, self.config.daemon.buffer_size), #queue='restore_tasks', retry=True, retry_policy={ 'max_retries': 3, 'interval_start': 3, 'interval_step': 0.5, 'interval_max': 0.5, }) except celery_tasks.backup.OperationalError as exc: self.log.info("Submitting celery task raised: %r", exc) print "Submitted restore" else: raise HTTPBadRequest("Invalid method") r = response(status=206) r.headers["Location"] = "/tasks/%s" % ctask.id return r
def get_ticket(uuid): return tickets.get(uuid).info()