def show_instance_details(instance_id): """ Shows the details of a Packet Tracer instance. --- tags: - instance parameters: - name: instance_id in: path type: integer description: instance identifier required: true responses: 200: description: Details of the instance schema: $ref: '#/definitions/assign_instance_post_Instance' 404: description: There is not an instance for the given instance_id. schema: $ref: '#/definitions/allocate_instance_post_Error' """ instance = Instance.get(instance_id) if instance is None: return not_found(error="The instance does not exist.") return jsonify(instance.serialize(request.base_url, get_host()))
def try_restart_on_exited_containers(): docker = get_docker_client() restarted_instances = [] # 'exited': 0 throws exception, 'exited': '0' does not work. # Because of this I have felt forced to use regular expressions :-( pattern = re.compile(r"Exited [(](\d+)[)]") for container in docker.containers(filters={'status': 'exited'}): # Ignore containers not created from image 'packettracer' if container.get('Image')=='packettracer': match = pattern.match(container.get('Status')) if match: # Stopped containers only container_id = container.get('Id') instance = Instance.get_by_docker_id(container_id) if instance: if match.group(1)=='0': # Restart stopped containers (which exited successfully) instance.mark_starting() try: logger.info('Restarting %s.' % instance) docker.start(container=container_id) wait_for_ready_container.s(instance.id).delay() restarted_instances.append(instance.id) except APIError as ae: logger.error('Error restarting container.') logger.error('Docker API exception. %s.' % ae) instance.mark_error() else: # TODO Check more thoroughly if containers with other # type of exit errors could be restarted too. instance.mark_error() return restarted_instances
def delete_instance(instance_id): """ Stops a running Packet Tracer instance. --- tags: - instance parameters: - name: instance_id in: path description: instance identifier required: true type: integer responses: 200: description: Instance stopped schema: $ref: '#/definitions/assign_instance_post_Instance' 404: description: There is not an instance for the given instance_id. schema: $ref: '#/definitions/allocate_instance_post_Error' 503: description: The instance has not been deleted in the given time. schema: $ref: '#/definitions/allocate_instance_post_Error' """ instance = Instance.get(instance_id) if not instance or not instance.is_active(): return not_found(error="The instance does not exist.") result = tasks.remove_container.delay(instance.docker_id) result.get() instance.delete() # TODO update instance object as status has changed return jsonify(instance.serialize(request.base_url, get_host()))
def assign_instance(): """ Creates a new Packet Tracer instance. --- tags: - instance responses: 201: description: Packet Tracer instance created schema: id: Instance properties: id: type: integer description: Identifier of the instance dockerId: type: string description: Identifier of the docker container which serves the instance url: type: string description: URL to handle the instance packetTracer: type: string description: Host and port where the Packet Tracer instance can be contacted (through IPC) vnc: type: string description: VNC URL to access the Packet Tracer instance createdAt: type: string format: date-time description: When was the instance created? deletedAt: type: string format: date-time description: When was the instance removed/stopped? status: type: string enum: [all, starting, deallocated, allocated, running, finished, error] description: Show status of the given instance 500: description: The container could not be created, there was an error. schema: $ref: '#/definitions/allocate_instance_post_Error' 503: description: At the moment the server cannot create more instances. schema: $ref: '#/definitions/allocate_instance_post_Error' """ try: result = tasks.create_instance.delay() instance_id = result.get() if instance_id: instance = Instance.get(instance_id) return jsonify( instance.serialize("%s/%d" % (request.base_url, instance.id), get_host())) return unavailable() except DockerContainerError as e: return internal_error(e.args[0])
def assign_instance(): """ Creates a new Packet Tracer instance. --- tags: - instance responses: 201: description: Packet Tracer instance created schema: id: Instance properties: id: type: integer description: Identifier of the instance dockerId: type: string description: Identifier of the docker container which serves the instance url: type: string description: URL to handle the instance packetTracer: type: string description: Host and port where the Packet Tracer instance can be contacted (through IPC) vnc: type: string description: VNC URL to access the Packet Tracer instance createdAt: type: string format: date-time description: When was the instance created? deletedAt: type: string format: date-time description: When was the instance removed/stopped? status: type: string enum: [all, starting, deallocated, allocated, running, finished, error] description: Show status of the given instance 500: description: The container could not be created, there was an error. schema: $ref: '#/definitions/allocate_instance_post_Error' 503: description: At the moment the server cannot create more instances. schema: $ref: '#/definitions/allocate_instance_post_Error' """ try: result = tasks.create_instance.delay() instance_id = result.get() if instance_id: instance = Instance.get(instance_id) return jsonify(instance.serialize("%s/%d" % (request.base_url, instance.id), get_host())) return unavailable() except DockerContainerError as e: return internal_error(e.args[0])
def delete_erroneous(not_delete=[]): deleted_instances = [] for erroneous_instance in Instance.get_erroneous(): if erroneous_instance.id not in not_delete: logger.info('Deleting erroneous %s.' % erroneous_instance) erroneous_instance.delete() deleted_instances.append(erroneous_instance.id) # Very conservative approach: # we remove it even if it might still be usable. remove_container.s(erroneous_instance.docker_id).delay() return deleted_instances
def deallocate_instance(instance_id): """Marks instance as deallocated and pauses the associated container.""" logger.info('Deallocating instance %s.' % instance_id) instance = Instance.get(instance_id) try: docker = get_docker_client() docker.pause(instance.docker_id) instance.deallocate() except APIError as ae: logger.error('Error deallocating instance %s.' % instance_id) logger.error('Docker API exception. %s.' % ae) # e.g., if it was already paused instance.mark_error()
def allocate_instance(): """Unpauses available container and marks associated instance as allocated.""" logger.info('Allocating instance.') docker = get_docker_client() error_discovered = False allocation_id = None for instance in Instance.get_deallocated(): try: docker.unpause(instance.docker_id) allocation_id = instance.allocate().id break except APIError as ae: logger.error('Error allocating instance %s.' % instance.id) logger.error('Docker API exception. %s.' % ae) # e.g., if it was already unpaused or it has been stopped instance.mark_error() if not allocation_id: # If there were no instances available, consider the creation of a new one instance_id = create_instance.s()() # Execute task inline allocation_id = Instance.get(instance_id).allocate().id return allocation_id
def list_instances(): """ Lists instances. --- tags: - instance parameters: - name: show in: query type: string description: Show different types of instances default: running enum: [all, starting, deallocated, allocated, running, finished, error] responses: 200: description: Packet Tracer instances schema: properties: instances: type: array items: $ref: '#/definitions/assign_instance_post_Instance' """ show_param = request.args.get("show") if show_param is None or show_param == "running": # default option return get_json_instances(Instance.get_running()) else: states = ("all", "starting", "deallocated", "allocated", "running", "finished", "error") if show_param not in states: state_enum = "[" for s in states: state_enum += "%s, " % s state_enum = state_enum[:-2] + "]" return BadRequest( "The 'show' parameter must contain one of the following values: %s." % state_enum) if show_param == "all": return get_json_instances(Instance.get_all()) # .limit(10) elif show_param == "starting": return get_json_instances(Instance.get_starting()) elif show_param == "deallocated": return get_json_instances(Instance.get_deallocated()) elif show_param == "allocated": return get_json_instances(Instance.get_allocated()) elif show_param == "error": return get_json_instances(Instance.get_erroneous()) else: # show_param is "finished": return get_json_instances(Instance.get_finished())
def list_instances(): """ Lists instances. --- tags: - instance parameters: - name: show in: query type: string description: Show different types of instances default: running enum: [all, starting, deallocated, allocated, running, finished, error] responses: 200: description: Packet Tracer instances schema: properties: instances: type: array items: $ref: '#/definitions/assign_instance_post_Instance' """ show_param = request.args.get("show") if show_param is None or show_param == "running": # default option return get_json_instances(Instance.get_running()) else: states = ("all", "starting", "deallocated", "allocated", "running", "finished", "error") if show_param not in states: state_enum = "[" for s in states: state_enum += "%s, " % s state_enum = state_enum[:-2] + "]" return BadRequest("The 'show' parameter must contain one of the following values: %s." % state_enum) if show_param == "all": return get_json_instances(Instance.get_all()) # .limit(10) elif show_param == "starting": return get_json_instances(Instance.get_starting()) elif show_param == "deallocated": return get_json_instances(Instance.get_deallocated()) elif show_param == "allocated": return get_json_instances(Instance.get_allocated()) elif show_param == "error": return get_json_instances(Instance.get_erroneous()) else: # show_param is "finished": return get_json_instances(Instance.get_finished())
def create_instance(): """Runs a new packettracer container in the specified port and create associated instance.""" logger.info('Creating new container.') pt_port = allocate_port() vnc_port_number = pt_port.number + 10000 try: container_id = start_container(pt_port.number, vnc_port_number) logger.info('Container started: %s' % container_id) # If success... instance = Instance.create(container_id, pt_port.number, vnc_port_number) pt_port.assign(instance.id) wait_for_ready_container.s(instance.id).delay() return instance.id except DockerContainerError as e: pt_port.release() raise e
def deallocate_instance(allocation_id): """ Stops a running Packet Tracer instance. --- tags: - allocation parameters: - name: allocation_id in: path type: integer description: allocation identifier required: true responses: 200: description: Allocation removed schema: $ref: '#/definitions/allocate_instance_post_Allocation' 404: description: There is not an allocation for the given allocation_id. schema: $ref: '#/definitions/allocate_instance_post_Error' """ instance = Instance.get_by_allocation_id(allocation_id) if not instance: return not_found(error="The allocation does not exist.") try: allocation_id = instance.allocated_by result = tasks.deallocate_instance.apply_async(args=(instance.id, )) result.get() allocation = Allocation.get(allocation_id) if allocation: # TODO update instance object as status has changed return jsonify(allocation.serialize(request.base_url, get_host())) # else return not_found(error="The allocation does not exist.") except Exception as e: return internal_error(e.args[0])
def deallocate_instance(allocation_id): """ Stops a running Packet Tracer instance. --- tags: - allocation parameters: - name: allocation_id in: path type: integer description: allocation identifier required: true responses: 200: description: Allocation removed schema: $ref: '#/definitions/allocate_instance_post_Allocation' 404: description: There is not an allocation for the given allocation_id. schema: $ref: '#/definitions/allocate_instance_post_Error' """ instance = Instance.get_by_allocation_id(allocation_id) if not instance: return not_found(error="The allocation does not exist.") try: allocation_id = instance.allocated_by result = tasks.deallocate_instance.apply_async(args=(instance.id,)) result.get() allocation = Allocation.get(allocation_id) if allocation: # TODO update instance object as status has changed return jsonify(allocation.serialize(request.base_url, get_host())) # else return not_found(error="The allocation does not exist.") except Exception as e: return internal_error(e.args[0])
def wait_for_ready_container(instance_id, timeout=2): """Waits for an instance to be ready (e.g., answer). Otherwise, marks it as erroneous .""" logger.info('Waiting for container to be ready.') instance = Instance.get(instance_id) container_running = is_container_running(instance.docker_id) if container_running: is_running = ptchecker.is_running(app.config['PT_CHECKER'], 'localhost', instance.pt_port, float(timeout)) if is_running: instance.mark_ready() if not instance.is_allocated(): # TODO rename the following task as it sounds confusing. # We call it here to pause the instance, not to "deallocate it". deallocate_instance.s(instance_id).delay() else: try: raise wait_for_ready_container.retry() except MaxRetriesExceededError: instance.mark_error() else: # If the container is not even running, PT won't answer no matter # how many times we try... instance.mark_error() return instance_id