def console(vm, console_type): """Arrange for an OOB console of the specified type This method arranges for an OOB console of the specified type. Only consoles of type "vnc" are supported for now. It uses a running instance of vncauthproxy to setup proper VNC forwarding with a random password, then returns the necessary VNC connection info to the caller. """ log.info("Get console VM %s, type %s", vm, console_type) # Use RAPI to get VNC console information for this instance if vm.operstate != "STARTED": raise faults.BadRequest('Server not in ACTIVE state.') if settings.TEST: console_data = {'kind': 'vnc', 'host': 'ganeti_node', 'port': 1000} else: console_data = backend.get_instance_console(vm) if console_data['kind'] != 'vnc': message = 'got console of kind %s, not "vnc"' % console_data['kind'] raise faults.ServiceUnavailable(message) # Let vncauthproxy decide on the source port. # The alternative: static allocation, e.g. # sport = console_data['port'] - 1000 sport = 0 daddr = console_data['host'] dport = console_data['port'] password = util.random_password() if settings.TEST: fwd = {'source_port': 1234, 'status': 'OK'} else: vnc_extra_opts = settings.CYCLADES_VNCAUTHPROXY_OPTS fwd = request_vnc_forwarding(sport, daddr, dport, password, **vnc_extra_opts) if fwd['status'] != "OK": raise faults.ServiceUnavailable('vncauthproxy returned error status') # Verify that the VNC server settings haven't changed if not settings.TEST: if console_data != backend.get_instance_console(vm): raise faults.ServiceUnavailable('VNC Server settings changed.') console = { 'type': 'vnc', 'host': getfqdn(), 'port': fwd['source_port'], 'password': password } return console
def get_client(self): """Get or create a client. """ if self.offline: raise faults.ServiceUnavailable("Backend '%s' is offline" % self) return get_rapi_client(self.id, self.hash, self.clustername, self.port, self.username, self.password)
def allocate_new_server(userid, flavor): """Allocate a new server to a Ganeti backend. Allocation is performed based on the owner of the server and the specified flavor. Also, backends that do not have a public IPv4 address are excluded from server allocation. This function runs inside a transaction, because after allocating the instance a commit must be performed in order to release all locks. """ backend_allocator = BackendAllocator() use_backend = backend_allocator.allocate(userid, flavor) if use_backend is None: log.error("No available backend for VM with flavor %s", flavor) raise faults.ServiceUnavailable("No available backends") return use_backend
def pooled_rapi_client(obj): if isinstance(obj, (VirtualMachine, BackendNetwork)): backend = obj.backend else: backend = obj if backend.offline: log.warning("Trying to connect with offline backend: %s", backend) raise faults.ServiceUnavailable("Cannot connect to offline" " backend: %s" % backend) b = backend client = get_rapi_client(b.id, b.hash, b.clustername, b.port, b.username, b.password) try: yield client finally: put_rapi_client(client)
def start_action(vm, action): log.debug("Applying action %s to VM %s", action, vm) if not action: return if not action in [x[0] for x in VirtualMachine.ACTIONS]: raise faults.ServiceUnavailable("Action %s not supported" % action) # No actions to deleted VMs if vm.deleted: raise faults.BadRequest("VirtualMachine has been deleted.") # No actions to machines being built. They may be destroyed, however. if vm.operstate == 'BUILD' and action != 'DESTROY': raise faults.BuildInProgress("Server is being build.") vm.action = action vm.backendjobid = None vm.backendopcode = None vm.backendjobstatus = None vm.backendlogmsg = None vm.save()
def create(userid, name, flavor, link=None, mac_prefix=None, mode=None, floating_ip_pool=False, tags=None, public=False, drained=False, project=None, shared_to_project=False): if flavor is None: raise faults.BadRequest("Missing request parameter 'type'") elif flavor not in Network.FLAVORS.keys(): raise faults.BadRequest("Invalid network type '%s'" % flavor) if mac_prefix is not None and flavor == "MAC_FILTERED": raise faults.BadRequest("Cannot override MAC_FILTERED mac-prefix") if link is not None and flavor == "PHYSICAL_VLAN": raise faults.BadRequest("Cannot override PHYSICAL_VLAN link") utils.check_name_length(name, Network.NETWORK_NAME_LENGTH, "Network name " "is too long") try: fmode, flink, fmac_prefix, ftags = util.values_from_flavor(flavor) except EmptyPool: log.error("Failed to allocate resources for network of type: %s", flavor) msg = "Failed to allocate resources for network." raise faults.ServiceUnavailable(msg) mode = mode or fmode link = link or flink mac_prefix = mac_prefix or fmac_prefix tags = tags or ftags validate_mac(mac_prefix + "0:00:00:00") # Check that given link is unique! if (link is not None and flavor == "IP_LESS_ROUTED" and Network.objects.filter(deleted=False, mode=mode, link=link).exists()): msg = "Link '%s' is already used." % link raise faults.BadRequest(msg) if project is None: project = userid network = Network.objects.create(name=name, userid=userid, project=project, shared_to_project=shared_to_project, flavor=flavor, mode=mode, link=link, mac_prefix=mac_prefix, tags=tags, public=public, external_router=public, floating_ip_pool=floating_ip_pool, action='CREATE', state='ACTIVE', drained=drained) if link is None: network.link = "%slink-%d" % (settings.BACKEND_PREFIX_ID, network.id) network.save() # Issue commission to Quotaholder and accept it since at the end of # this transaction the Network object will be created in the DB. # Note: the following call does a commit! if not public: quotas.issue_and_accept_commission(network) return network
def create_network(request): # Normal Response Code: 202 # Error Response Codes: computeFault (400, 500), # serviceUnavailable (503), # unauthorized (401), # badMediaType(415), # badRequest (400), # forbidden (403) # overLimit (413) try: req = utils.get_request_dict(request) log.info('create_network %s', req) user_id = request.user_uniq try: d = req['network'] name = d['name'] except KeyError: raise faults.BadRequest("Malformed request") # Get and validate flavor. Flavors are still exposed as 'type' in the # API. flavor = d.get("type", None) if flavor is None: raise faults.BadRequest("Missing request parameter 'type'") elif flavor not in Network.FLAVORS.keys(): raise faults.BadRequest("Invalid network type '%s'" % flavor) elif flavor not in settings.API_ENABLED_NETWORK_FLAVORS: raise faults.Forbidden("Can not create network of type '%s'" % flavor) public = d.get("public", False) if public: raise faults.Forbidden("Can not create a public network.") dhcp = d.get('dhcp', True) # Get and validate network parameters subnet = d.get('cidr', '192.168.1.0/24') subnet6 = d.get('cidr6', None) gateway = d.get('gateway', None) gateway6 = d.get('gateway6', None) # Check that user provided a valid subnet util.validate_network_params(subnet, gateway, subnet6, gateway6) try: mode, link, mac_prefix, tags = util.values_from_flavor(flavor) validate_mac(mac_prefix + "0:00:00:00") network = Network.objects.create( name=name, userid=user_id, subnet=subnet, subnet6=subnet6, gateway=gateway, gateway6=gateway6, dhcp=dhcp, flavor=flavor, mode=mode, link=link, mac_prefix=mac_prefix, tags=tags, action='CREATE', state='ACTIVE') except EmptyPool: log.error("Failed to allocate resources for network of type: %s", flavor) raise faults.ServiceUnavailable("Failed to allocate network" " resources") # Issue commission to Quotaholder and accept it since at the end of # this transaction the Network object will be created in the DB. # Note: the following call does a commit! quotas.issue_and_accept_commission(network) except: transaction.rollback() raise else: transaction.commit() networkdict = network_to_dict(network, request.user_uniq) response = render_network(request, networkdict, status=202) return response
def console(vm, console_type): """Arrange for an OOB console of the specified type This method arranges for an OOB console of the specified type. Only consoles of type "vnc" are supported for now. It uses a running instance of vncauthproxy to setup proper VNC forwarding with a random password, then returns the necessary VNC connection info to the caller. """ log.info("Get console VM %s, type %s", vm, console_type) if vm.operstate != "STARTED": raise faults.BadRequest('Server not in ACTIVE state.') # Use RAPI to get VNC console information for this instance # RAPI GetInstanceConsole() returns endpoints to the vnc_bind_address, # which is a cluster-wide setting, either 0.0.0.0 or 127.0.0.1, and pretty # useless (see #783). # # Until this is fixed on the Ganeti side, construct a console info reply # directly. # # WARNING: This assumes that VNC runs on port network_port on # the instance's primary node, and is probably # hypervisor-specific. def get_console_data(i): return {"kind": "vnc", "host": i["pnode"], "port": i["network_port"]} with pooled_rapi_client(vm) as c: i = c.GetInstance(vm.backend_vm_id) console_data = get_console_data(i) if vm.backend.hypervisor == "kvm" and i['hvparams']['serial_console']: raise Exception("hv parameter serial_console cannot be true") # Check that the instance is really running if not i["oper_state"]: log.warning("VM '%s' is marked as '%s' in DB while DOWN in Ganeti", vm.id, vm.operstate) # Instance is not running. Mock a shutdown job to sync DB backend.process_op_status(vm, etime=datetime.now(), jobid=0, opcode="OP_INSTANCE_SHUTDOWN", status="success", logmsg="Reconciliation simulated event") raise faults.BadRequest('Server not in ACTIVE state.') # Let vncauthproxy decide on the source port. # The alternative: static allocation, e.g. # sport = console_data['port'] - 1000 sport = 0 daddr = console_data['host'] dport = console_data['port'] password = util.random_password() vnc_extra_opts = settings.CYCLADES_VNCAUTHPROXY_OPTS # Maintain backwards compatibility with the dict setting if isinstance(vnc_extra_opts, list): vnc_extra_opts = choice(vnc_extra_opts) fwd = request_vnc_forwarding(sport, daddr, dport, password, console_type=console_type, **vnc_extra_opts) if fwd['status'] != "OK": log.error("vncauthproxy returned error status: '%s'" % fwd) raise faults.ServiceUnavailable('vncauthproxy returned error status') # Verify that the VNC server settings haven't changed with pooled_rapi_client(vm) as c: i = c.GetInstance(vm.backend_vm_id) if get_console_data(i) != console_data: raise faults.ServiceUnavailable('VNC Server settings changed.') try: host = fwd['proxy_address'] except KeyError: host = getfqdn() console = { 'type': console_type, 'host': host, 'port': fwd['source_port'], 'password': password } return console
def get_console(request, vm, args): """Arrange for an OOB console of the specified type This method arranges for an OOB console of the specified type. Only consoles of type "vnc" are supported for now. It uses a running instance of vncauthproxy to setup proper VNC forwarding with a random password, then returns the necessary VNC connection info to the caller. """ # Normal Response Code: 200 # Error Response Codes: computeFault (400, 500), # serviceUnavailable (503), # unauthorized (401), # badRequest (400), # badMediaType(415), # itemNotFound (404), # buildInProgress (409), # overLimit (413) log.info("Get console VM %s", vm) console_type = args.get('type', '') if (console_type != 'vnc' and console_type != 'wsvnc'): raise faults.BadRequest('Type can only be "vnc" or "wsvnc."') # Use RAPI to get VNC console information for this instance if get_rsapi_state(vm) != 'ACTIVE': raise faults.BadRequest('Server not in ACTIVE state.') if settings.TEST: console_data = {'kind': 'vnc', 'host': 'ganeti_node', 'port': 1000} else: console_data = backend.get_instance_console(vm) if console_data['kind'] != 'vnc': message = 'got console of kind %s, not "vnc"' % console_data['kind'] raise faults.ServiceUnavailable(message) # Let vncauthproxy decide on the source port. # The alternative: static allocation, e.g. # sport = console_data['port'] - 1000 sport = 27631 daddr = console_data['host'] dport = console_data['port'] password = random_password() if settings.TEST: fwd = {'source_port': 1234, 'status': 'OK'} else: fwd = request_vnc_forwarding(sport, daddr, dport, password, auth_user="******", auth_password="******", console_type=console_type) #fwd = request_vnc_forwarding(sport, daddr, dport, password) if fwd['status'] != "OK": raise faults.ServiceUnavailable('vncauthproxy returned error status') # Verify that the VNC server settings haven't changed if not settings.TEST: if console_data != backend.get_instance_console(vm): raise faults.ServiceUnavailable('VNC Server settings changed.') console = { 'type': console_type, 'host': getfqdn(), 'port': fwd['source_port'], 'password': password } if request.serialization == 'xml': mimetype = 'application/xml' data = render_to_string('console.xml', {'console': console}) else: mimetype = 'application/json' data = json.dumps({'console': console}) return HttpResponse(data, mimetype=mimetype, status=200)
def get_client(self): if self.backend: return self.backend.get_client() else: raise faults.ServiceUnavailable("VirtualMachine without backend")
def do_create_server(userid, name, password, flavor, image, metadata={}, personality=[], network=None, backend=None): # Fix flavor for archipelago disk_template, provider = util.get_flavor_provider(flavor) if provider: flavor.disk_template = disk_template flavor.disk_provider = provider flavor.disk_origin = image['checksum'] image['backend_id'] = 'null' else: flavor.disk_provider = None flavor.disk_origin = None try: if backend is None: # Allocate backend to host the server. backend_allocator = BackendAllocator() backend = backend_allocator.allocate(userid, flavor) if backend is None: log.error("No available backend for VM with flavor %s", flavor) raise faults.ServiceUnavailable("No available backends") if network is None: # Allocate IP from public network (network, address) = util.get_public_ip(backend) nic = {'ip': address, 'network': network.backend_id} else: address = util.get_network_free_address(network) # We must save the VM instance now, so that it gets a valid # vm.backend_vm_id. vm = VirtualMachine.objects.create(name=name, backend=backend, userid=userid, imageid=image["id"], flavor=flavor, action="CREATE") # Create VM's public NIC. Do not wait notification form ganeti hooks to # create this NIC, because if the hooks never run (e.g. building error) # the VM's public IP address will never be released! NetworkInterface.objects.create(machine=vm, network=network, index=0, ipv4=address, state="BUILDING") log.info("Created entry in DB for VM '%s'", vm) # dispatch server created signal server_created.send(sender=vm, created_vm_params={ 'img_id': image['backend_id'], 'img_passwd': password, 'img_format': str(image['format']), 'img_personality': json.dumps(personality), 'img_properties': json.dumps(image['metadata']), }) # Also we must create the VM metadata in the same transaction. for key, val in metadata.items(): VirtualMachineMetadata.objects.create(meta_key=key, meta_value=val, vm=vm) # Issue commission to Quotaholder and accept it since at the end of # this transaction the VirtualMachine object will be created in the DB. # Note: the following call does a commit! quotas.issue_and_accept_commission(vm) except: transaction.rollback() raise else: transaction.commit() try: jobID = create_instance(vm, nic, flavor, image) # At this point the job is enqueued in the Ganeti backend vm.backendjobid = jobID vm.save() transaction.commit() log.info("User %s created VM %s, NIC %s, Backend %s, JobID %s", userid, vm, nic, backend, str(jobID)) except GanetiApiError as e: log.exception("Can not communicate to backend %s: %s.", backend, e) # Failed while enqueuing OP_INSTANCE_CREATE to backend. Restore # already reserved quotas by issuing a negative commission vm.operstate = "ERROR" vm.backendlogmsg = "Can not communicate to backend." vm.deleted = True vm.save() quotas.issue_and_accept_commission(vm, delete=True) raise except: transaction.rollback() raise return vm