def list_machines(request): """Gets machines and their metadata for a backend. Because each provider stores metadata in different places several checks are needed. The folowing are considered::: * For tags, Rackspace stores them in extra.metadata.tags while EC2 in extra.tags.tags. * For images, both EC2 and Rackpace have an image and an etra.imageId attribute * For flavors, EC2 has an extra.instancetype attribute while Rackspace an extra.flavorId. however we also expect to get size attribute. """ try: conn = connect(request) except RuntimeError as e: log.error(e) return Response('Internal server error: %s' % e, 503) except: return Response('Backend not found', 404) try: machines = conn.list_nodes() except: return Response('Backend unavailable', 503) ret = [] for m in machines: tags = m.extra.get('tags', None) or m.extra.get('metadata', None) tags = tags or {} tags = [value for key, value in tags.iteritems() if key != 'Name'] if m.extra.get('availability', None): # for EC2 tags.append(m.extra['availability']) elif m.extra.get('DATACENTERID', None): # for Linode tags.append(LINODE_DATACENTERS[m.extra['DATACENTERID']]) image_id = m.image or m.extra.get('imageId', None) size = m.size or m.extra.get('flavorId', None) size = size or m.extra.get('instancetype', None) machine = {'id' : m.id, 'uuid' : m.get_uuid(), 'name' : m.name, 'imageId' : image_id, 'size' : size, 'state' : STATES[m.state], 'private_ips' : m.private_ips, 'public_ips' : m.public_ips, 'tags' : tags, 'extra' : m.extra, } machine.update(get_machine_actions(m, conn)) ret.append(machine) return ret
def stop_machine(request): """Stops a machine on backends that support it. Currently only EC2 supports that. .. note:: Normally try won't get an AttributeError exception because this action is not allowed for machines that don't support it. Check helpers.get_machine_actions. """ try: conn = connect(request) except: return Response("Backend not found", 404) machine_id = request.matchdict["machine"] machine = Node(machine_id, name=machine_id, state=0, public_ips=[], private_ips=[], driver=conn) try: # In libcloud it is not possible to call this with machine.stop() conn.ex_stop_node(machine) return Response("Success", 200) except AttributeError: return Response("Action not supported for this machine", 404) except: return []
def destroy_machine(request): """Destroys a machine on a certain backend. After destroying a machine it also deletes all key associations. However, it doesn't undeploy the keypair. There is no need to do it because the machine will be destroyed. """ try: conn = connect(request) except: return Response("Backend not found", 404) machine_id = request.matchdict["machine"] machine = Node(machine_id, name=machine_id, state=0, public_ips=[], private_ips=[], driver=conn) machine.destroy() backend_id = request.matchdict["backend"] pair = [backend_id, machine_id] try: keypairs = request.environ["beaker.session"]["keypairs"] except: keypairs = request.registry.settings.get("keypairs", {}) for key in keypairs: machines = keypairs[key].get("machines", None) if pair in machines: disassociate_key(request, key, backend_id, machine_id, undeploy=False) return Response("Success", 200)
def list_sizes(request): """List sizes (aka flavors) from each backend.""" try: conn = connect(request) except: return Response('Backend not found', 404) try: sizes = conn.list_sizes() except: return Response('Backend unavailable', 503) ret = [] for size in sizes: ret.append({ 'id': size.id, 'bandwidth': size.bandwidth, 'disk': size.disk, 'driver': size.driver.name, 'name': size.name, 'price': size.price, 'ram': size.ram, }) return ret
def list_locations(request): """List locations from each backend. Locations mean different things in each backend. e.g. EC2 uses it as a datacenter in a given availability zone, whereas Linode lists availability zones. However all responses share id, name and country eventhough in some cases might be empty, e.g. Openstack. In EC2 all locations by a provider have the same name, so the availability zones are listed instead of name. """ try: conn = connect(request) except: return Response('Backend not found', 404) try: locations = conn.list_locations() except: return Response('Backend unavailable', 503) ret = [] for location in locations: if conn.type in EC2_PROVIDERS: name = location.availability_zone.name else: name = location.name ret.append({ 'id': location.id, 'name': name, 'country': location.country, }) return ret
def shell_command(request): """Send a shell command to a machine over ssh, using fabric.""" try: conn = connect(request) except: return Response('Backend not found', 404) machine_id = request.matchdict['machine'] backend_id = request.matchdict['backend'] host = request.params.get('host', None) ssh_user = request.params.get('ssh_user', None) command = request.params.get('command', None) if not ssh_user or ssh_user == 'undefined': ssh_user = '******' try: keypairs = request.environ['beaker.session']['keypairs'] except: keypairs = request.registry.settings.get('keypairs', {}) keypair = get_keypair(keypairs, backend_id, machine_id) if keypair: private_key = keypair['private'] public_key = keypair['public'] else: private_key = public_key = None return run_command(conn, machine_id, host, ssh_user, private_key, command)
def list_images(request): """List images from each backend.""" try: conn = connect(request) except: return Response('Backend not found', 404) try: if conn.type in EC2_PROVIDERS: images = conn.list_images(None, EC2_IMAGES[conn.type].keys()) for image in images: image.name = EC2_IMAGES[conn.type][image.id] else: images = conn.list_images() except: return Response('Backend unavailable', 503) ret = [] for image in images: ret.append({ 'id': image.id, 'extra': image.extra, 'name': image.name, }) return ret
def list_sizes(request): """List sizes (aka flavors) from each backend.""" try: conn = connect(request) except: return Response("Backend not found", 404) try: sizes = conn.list_sizes() except: return Response("Backend unavailable", 503) ret = [] for size in sizes: ret.append( { "id": size.id, "bandwidth": size.bandwidth, "disk": size.disk, "driver": size.driver.name, "name": size.name, "price": size.price, "ram": size.ram, } ) return ret
def shell_command(request): """Sends a shell command to a machine over ssh, using fabric. .. note:: Used for uptime only. """ try: conn = connect(request) except: return Response("Backend not found", 404) machine_id = request.matchdict["machine"] backend_id = request.matchdict["backend"] host = request.params.get("host", None) ssh_user = request.params.get("ssh_user", None) command = request.params.get("command", None) if not ssh_user or ssh_user == "undefined": ssh_user = "******" try: keypairs = request.environ["beaker.session"]["keypairs"] except: keypairs = request.registry.settings.get("keypairs", {}) keypair = get_keypair(keypairs, backend_id, machine_id) if keypair: private_key = keypair["private"] public_key = keypair["public"] else: private_key = public_key = None ret = run_command(conn, machine_id, host, ssh_user, private_key, command) return ret
def stop_machine(request): """Stops a machine on backends that support it. Currently only EC2 supports that. .. note:: Normally try won't get an AttributeError exception because this action is not allowed for machines that don't support it. Check helpers.get_machine_actions. """ try: conn = connect(request) except: return Response('Backend not found', 404) machine_id = request.matchdict['machine'] machine = Node(machine_id, name=machine_id, state=0, public_ips=[], private_ips=[], driver=conn) try: # In libcloud it is not possible to call this with machine.stop() conn.ex_stop_node(machine) Response('Success', 200) except AttributeError: return Response('Action not supported for this machine', 404) except: return []
def list_locations(request): """List locations from each backend. Locations mean different things in each backend. e.g. EC2 uses it as a datacenter in a given availability zone, whereas Linode lists availability zones. However all responses share id, name and country eventhough in some cases might be empty, e.g. Openstack. In EC2 all locations by a provider have the same name, so the availability zones are listed instead of name. """ try: conn = connect(request) except: return Response("Backend not found", 404) try: locations = conn.list_locations() except: return Response("Backend unavailable", 503) ret = [] for location in locations: if conn.type in EC2_PROVIDERS: name = location.availability_zone.name else: name = location.name ret.append({"id": location.id, "name": name, "country": location.country}) return ret
def shell_command(request): """Sends a shell command to a machine over ssh, using fabric. .. note:: Used for uptime only. """ try: conn = connect(request) except: return Response('Backend not found', 404) machine_id = request.matchdict['machine'] backend_id = request.matchdict['backend'] host = request.params.get('host', None) ssh_user = request.params.get('ssh_user', None) command = request.params.get('command', None) if not ssh_user or ssh_user == 'undefined': ssh_user = '******' try: keypairs = request.environ['beaker.session']['keypairs'] except: keypairs = request.registry.settings.get('keypairs', {}) keypair = get_keypair(keypairs, backend_id, machine_id) if keypair: private_key = keypair['private'] public_key = keypair['public'] else: private_key = public_key = None ret = run_command(conn, machine_id, host, ssh_user, private_key, command) return ret
def set_machine_metadata(request): """Sets metadata for a machine, given the backend and machine id. Libcloud handles this differently for each provider. Linode and Rackspace, at least the old Rackspace providers, don't support metadata adding. machine_id comes as u'...' but the rest are plain strings so use == when comparing in ifs. u'f' is 'f' returns false and 'in' is too broad. """ try: conn = connect(request) except: return Response('Backend not found', 404) if conn.type in [Provider.LINODE, Provider.RACKSPACE_FIRST_GEN]: return Response('Adding metadata is not supported in this provider', 501) machine_id = request.matchdict['machine'] try: tag = request.json_body['tag'] unique_key = 'mist.io_tag-' + datetime.now().isoformat() pair = {unique_key: tag} except: return Response('Malformed metadata format', 400) if conn.type in EC2_PROVIDERS: try: machine = Node(machine_id, name='', state=0, public_ips=[], private_ips=[], driver=conn) conn.ex_create_tags(machine, pair) except: return Response('Error while creating tag in EC2', 503) else: try: nodes = conn.list_nodes() for node in nodes: if node.id == machine_id: machine = node break except: return Response('Machine not found', 404) try: machine.extra['metadata'].update(pair) conn.ex_set_metadata(machine, pair) except: return Response('Error while creating tag', 503) return Response('Success', 200)
def reboot_machine(request): """Reboots a machine on a certain backend.""" try: conn = connect(request) except: return Response("Backend not found", 404) machine_id = request.matchdict["machine"] machine = Node(machine_id, name=machine_id, state=0, public_ips=[], private_ips=[], driver=conn) machine.reboot() return Response("Success", 200)
def __call__(self, environ, start_response): request = Request(environ) if request.path.endswith('shell') and request.method == 'GET': try: backend = self.app.routes_mapper(request)['match']['backend'] machine = self.app.routes_mapper(request)['match']['machine'] host = request.params.get('host', None) ssh_user = request.params.get('ssh_user', None) command = request.params.get('command', None) request.registry = self.app.registry if not ssh_user or ssh_user == 'undefined': log.debug("Will select root as the ssh-user as we don't know who we are") ssh_user = '******' try: keypairs = environ['beaker.session']['keypairs'] except: keypairs = request.registry.settings.get('keypairs', {}) preferred_keypairs = get_preferred_keypairs(keypairs, backend, machine) log.debug("preferred keypairs = %s" % preferred_keypairs) if preferred_keypairs: keypair = keypairs[preferred_keypairs[0]] private_key = keypair['private'] s_user = get_ssh_user_from_keypair(keypair, backend, machine) log.debug("get user from keypair returned: %s" % s_user) if s_user: ssh_user = s_user log.debug("Will select %s as the ssh-user" % ssh_user) else: private_key = None conn = connect(request, backend) if conn: return self.stream_command(conn, machine, host, ssh_user, private_key, command, start_response) else: raise except: # leave error handling up to the app return self.app(environ, start_response) else: return self.app(environ, start_response)
def destroy_machine(request): """Destroys a machine on a certain backend.""" try: conn = connect(request) except: return Response('Backend not found', 404) machine_id = request.matchdict['machine'] machine = Node(machine_id, name=machine_id, state=0, public_ips=[], private_ips=[], driver=conn) machine.destroy() return Response('Success', 200)
def list_images(request): """List images from each backend.""" try: conn = connect(request) except: return Response("Backend not found", 404) try: if conn.type in EC2_PROVIDERS: images = conn.list_images(None, EC2_IMAGES[conn.type].keys()) for image in images: image.name = EC2_IMAGES[conn.type][image.id] else: images = conn.list_images() except: return Response("Backend unavailable", 503) ret = [] for image in images: ret.append({"id": image.id, "extra": image.extra, "name": image.name}) return ret
def __call__(self, environ, start_response): request = Request(environ) if request.path.endswith('shell') and request.method == 'GET': try: backend = self.app.routes_mapper(request)['match']['backend'] machine = self.app.routes_mapper(request)['match']['machine'] host = request.params.get('host', None) ssh_user = request.params.get('ssh_user', None) command = request.params.get('command', None) request.registry = self.app.registry if not ssh_user or ssh_user == 'undefined': ssh_user = '******' try: keypairs = environ['beaker.session']['keypairs'] except: keypairs = request.registry.settings.get('keypairs', {}) keypair = get_keypair(keypairs, backend, machine) if keypair: private_key = keypair['private'] else: private_key = None conn = connect(request, backend) if conn: return self.stream_command(conn, machine, host, ssh_user, private_key, command, start_response) else: raise except: # leave error handling up to the app return self.app(environ, start_response) else: return self.app(environ, start_response)
def list_sizes(request): """List sizes (aka flavors) from each backend.""" try: conn = connect(request) except: return Response('Backend not found', 404) try: sizes = conn.list_sizes() except: return Response('Backend unavailable', 503) ret = [] for size in sizes: ret.append({'id' : size.id, 'bandwidth' : size.bandwidth, 'disk' : size.disk, 'driver' : size.driver.name, 'name' : size.name, 'price' : size.price, 'ram' : size.ram, }) return ret
def create_machine(request): """Creates a new virtual machine on the specified backend. If the backend is Rackspace it attempts to deploy the node with an ssh key provided in config. the method used is the only one working in the old Rackspace backend. create_node(), from libcloud.compute.base, with 'auth' kwarg doesn't do the trick. Didn't test if you can upload some ssh related files using the 'ex_files' kwarg from openstack 1.0 driver. In Linode creation is a bit different. There you can pass the key file directly during creation. The Linode API also requires to set a disk size and doesn't get it from size.id. So, send size.disk from the client and use it in all cases just to avoid provider checking. Finally, Linode API does not support association between a machine and the image it came from. We could set this, at least for machines created through mist.io in ex_comment, lroot or lconfig. lroot seems more appropriate. However, liblcoud doesn't support linode.config.list at the moment, so no way to get them. Also, it will create inconsistencies for machines created through mist.io and those from the Linode interface. """ try: conn = connect(request) except: return Response('Backend not found', 404) backend_id = request.matchdict['backend'] try: key_name = request.json_body['key'] except: key_name = None try: keypairs = request.environ['beaker.session']['keypairs'] except: keypairs = request.registry.settings.get('keypairs', {}) if key_name: keypair = get_keypair_by_name(keypairs, key_name) else: keypair = get_keypair(keypairs) if keypair: private_key = keypair['private'] public_key = keypair['public'] else: private_key = public_key = None try: machine_name = request.json_body['name'] location_id = request.json_body['location'] image_id = request.json_body['image'] size_id = request.json_body['size'] #deploy_script received as unicode, but ScriptDeployment wants str script = str(request.json_body.get('script', '')) # these are required only for Linode, passing them anyway image_extra = request.json_body['image_extra'] disk = request.json_body['disk'] except Exception as e: return Response('Invalid payload', 400) size = NodeSize(size_id, name='', ram='', disk=disk, bandwidth='', price='', driver=conn) image = NodeImage(image_id, name='', extra=image_extra, driver=conn) if conn.type in EC2_PROVIDERS: locations = conn.list_locations() for loc in locations: if loc.id == location_id: location = loc break else: location = NodeLocation(location_id, name='', country='', driver=conn) if conn.type in [Provider.RACKSPACE_FIRST_GEN, Provider.RACKSPACE] and\ public_key: key = SSHKeyDeployment(str(public_key)) deploy_script = ScriptDeployment(script) msd = MultiStepDeployment([key, deploy_script]) try: node = conn.deploy_node(name=machine_name, image=image, size=size, location=location, deploy=msd) if keypair: machines = keypair.get('machines', None) if machines and len(machines): keypair['machines'].append([backend_id, node.id]) else: keypair['machines'] = [ [backend_id, node.id], ] save_keypairs(request, keypair) except Exception as e: return Response( 'Something went wrong with node creation in RackSpace: %s' % e, 500) elif conn.type in EC2_PROVIDERS and public_key: imported_key = import_key(conn, public_key, key_name) created_security_group = create_security_group(conn, EC2_SECURITYGROUP) deploy_script = ScriptDeployment(script) (tmp_key, tmp_key_path) = tempfile.mkstemp() key_fd = os.fdopen(tmp_key, 'w+b') key_fd.write(private_key) key_fd.close() #deploy_node wants path for ssh private key if imported_key and created_security_group: try: node = conn.deploy_node( name=machine_name, image=image, size=size, deploy=deploy_script, location=location, ssh_key=tmp_key_path, ex_keyname=key_name, ex_securitygroup=EC2_SECURITYGROUP['name']) if keypair: machines = keypair.get('machines', None) if machines and len(machines): keypair['machines'].append([backend_id, node.id]) else: keypair['machines'] = [ [backend_id, node.id], ] save_keypairs(request, keypair) except Exception as e: return Response( 'Something went wrong with node creation in EC2: %s' % e, 500) #remove temp file with private key try: os.remove(tmp_key_path) except: pass elif conn.type is Provider.LINODE and public_key: auth = NodeAuthSSHKey(public_key) deploy_script = ScriptDeployment(script) try: node = conn.create_node(name=machine_name, image=image, size=size, deploy=deploy_script, location=location, auth=auth) if keypair: machines = keypair.get('machines', None) if machines and len(machines): keypair['machines'].append([backend_id, node.id]) else: keypair['machines'] = [ [backend_id, node.id], ] save_keypairs(request, keypair) except: return Response('Something went wrong with Linode creation', 500) else: try: node = conn.create_node(name=machine_name, image=image, size=size, location=location) except Exception as e: return Response( 'Something went wrong with generic node creation: %s' % e, 500) return { 'id': node.id, 'name': node.name, 'extra': node.extra, 'public_ips': node.public_ips, 'private_ips': node.private_ips, }
def delete_machine_metadata(request): """Delete metadata for a machine, given the machine id and the tag to be deleted. Libcloud handles this differently for each provider. Linode and Rackspace, at least the old Rackspace providers, don't support metadata updating. In EC2 you can delete just the tag you like. In Openstack you can only set a new list and not delete from the existing. Mist.io client knows only the value of the tag and not it's key so it has to loop through the machine list in order to find it. Don't forget to check string encoding before using them in ifs. u'f' is 'f' returns false. """ try: conn = connect(request) except: return Response('Backend not found', 404) if conn.type in [Provider.LINODE, Provider.RACKSPACE_FIRST_GEN]: return Response('Updating metadata is not supported in this provider', 501) try: tag = request.json_body['tag'] except: return Response('Malformed metadata format', 400) machine_id = request.matchdict['machine'] try: nodes = conn.list_nodes() for node in nodes: if node.id == machine_id: machine = node break except: return Response('Machine not found', 404) if conn.type in EC2_PROVIDERS: tags = machine.extra.get('tags', None) try: for mkey, mdata in tags.iteritems(): if tag == mdata: pair = {mkey: tag} break except: return Response('Tag not found', 404) try: conn.ex_delete_tags(machine, pair) except: return Response('Error while deleting metadata in EC2', 503) else: tags = machine.extra.get('metadata', None) try: for mkey, mdata in tags.iteritems(): if tag == mdata: tags.pop(mkey) break except: return Response('Tag not found', 404) try: conn.ex_set_metadata(machine, tags) except: return Response('Error while updating metadata', 503) return Response('Success', 200)
def delete_machine_metadata(request): """Deletes metadata for a machine, given the machine id and the tag to be deleted. Libcloud handles this differently for each provider. Linode and Rackspace, at least the old Rackspace providers, don't support metadata updating. In EC2 you can delete just the tag you like. In Openstack you can only set a new list and not delete from the existing. Mist.io client knows only the value of the tag and not it's key so it has to loop through the machine list in order to find it. Don't forget to check string encoding before using them in ifs. u'f' is 'f' returns false. """ try: conn = connect(request) except: return Response("Backend not found", 404) if conn.type in [Provider.LINODE, Provider.RACKSPACE_FIRST_GEN]: return Response("Updating metadata is not supported in this provider", 501) try: tag = request.json_body["tag"] except: return Response("Malformed metadata format", 400) machine_id = request.matchdict["machine"] try: nodes = conn.list_nodes() for node in nodes: if node.id == machine_id: machine = node break except: return Response("Machine not found", 404) if conn.type in EC2_PROVIDERS: tags = machine.extra.get("tags", None) try: for mkey, mdata in tags.iteritems(): if tag == mdata: pair = {mkey: tag} break except: return Response("Tag not found", 404) try: conn.ex_delete_tags(machine, pair) except: return Response("Error while deleting metadata in EC2", 503) else: tags = machine.extra.get("metadata", None) try: for mkey, mdata in tags.iteritems(): if tag == mdata: tags.pop(mkey) break except: return Response("Tag not found", 404) try: conn.ex_set_metadata(machine, tags) except: return Response("Error while updating metadata", 503) return Response("Success", 200)
def create_machine(request): """Creates a new virtual machine on the specified backend. If the backend is Rackspace it attempts to deploy the node with an ssh key provided in config. the method used is the only one working in the old Rackspace backend. create_node(), from libcloud.compute.base, with 'auth' kwarg doesn't do the trick. Didn't test if you can upload some ssh related files using the 'ex_files' kwarg from openstack 1.0 driver. In Linode creation is a bit different. There you can pass the key file directly during creation. The Linode API also requires to set a disk size and doesn't get it from size.id. So, send size.disk from the client and use it in all cases just to avoid provider checking. Finally, Linode API does not support association between a machine and the image it came from. We could set this, at least for machines created through mist.io in ex_comment, lroot or lconfig. lroot seems more appropriate. However, liblcoud doesn't support linode.config.list at the moment, so no way to get them. Also, it will create inconsistencies for machines created through mist.io and those from the Linode interface. """ try: conn = connect(request) except: return Response('Backend not found', 404) backend_id = request.matchdict['backend'] try: key_name = request.json_body['key'] except: key_name = None try: keypairs = request.environ['beaker.session']['keypairs'] except: keypairs = request.registry.settings.get('keypairs', {}) if key_name: keypair = get_keypair_by_name(keypairs, key_name) else: keypair = get_keypair(keypairs) if keypair: private_key = keypair['private'] public_key = keypair['public'] else: private_key = public_key = None try: machine_name = request.json_body['name'] location_id = request.json_body['location'] image_id = request.json_body['image'] size_id = request.json_body['size'] #deploy_script received as unicode, but ScriptDeployment wants str script = str(request.json_body.get('script', '')) # these are required only for Linode, passing them anyway image_extra = request.json_body['image_extra'] disk = request.json_body['disk'] except Exception as e: return Response('Invalid payload', 400) size = NodeSize(size_id, name='', ram='', disk=disk, bandwidth='', price='', driver=conn) image = NodeImage(image_id, name='', extra=image_extra, driver=conn) if conn.type in EC2_PROVIDERS: locations = conn.list_locations() for loc in locations: if loc.id == location_id: location = loc break else: location = NodeLocation(location_id, name='', country='', driver=conn) if conn.type in [Provider.RACKSPACE_FIRST_GEN, Provider.RACKSPACE] and\ public_key: key = SSHKeyDeployment(str(public_key)) deploy_script = ScriptDeployment(script) msd = MultiStepDeployment([key, deploy_script]) try: node = conn.deploy_node(name=machine_name, image=image, size=size, location=location, deploy=msd) if keypair: machines = keypair.get('machines', None) if machines and len(machines): keypair['machines'].append([backend_id, node.id]) else: keypair['machines'] = [[backend_id, node.id],] save_keypairs(request, keypair) except Exception as e: return Response('Something went wrong with node creation in RackSpace: %s' % e, 500) elif conn.type in EC2_PROVIDERS and public_key: imported_key = import_key(conn, public_key, key_name) created_security_group = create_security_group(conn, EC2_SECURITYGROUP) deploy_script = ScriptDeployment(script) (tmp_key, tmp_key_path) = tempfile.mkstemp() key_fd = os.fdopen(tmp_key, 'w+b') key_fd.write(private_key) key_fd.close() #deploy_node wants path for ssh private key if imported_key and created_security_group: try: node = conn.deploy_node(name=machine_name, image=image, size=size, deploy=deploy_script, location=location, ssh_key=tmp_key_path, ex_keyname=key_name, ex_securitygroup=EC2_SECURITYGROUP['name']) if keypair: machines = keypair.get('machines', None) if machines and len(machines): keypair['machines'].append([backend_id, node.id]) else: keypair['machines'] = [[backend_id, node.id],] save_keypairs(request, keypair) except Exception as e: return Response('Something went wrong with node creation in EC2: %s' % e, 500) #remove temp file with private key try: os.remove(tmp_key_path) except: pass elif conn.type is Provider.LINODE and public_key: auth = NodeAuthSSHKey(public_key) deploy_script = ScriptDeployment(script) try: node = conn.create_node(name=machine_name, image=image, size=size, deploy=deploy_script, location=location, auth=auth) if keypair: machines = keypair.get('machines', None) if machines and len(machines): keypair['machines'].append([backend_id, node.id]) else: keypair['machines'] = [[backend_id, node.id],] save_keypairs(request, keypair) except: return Response('Something went wrong with Linode creation', 500) else: try: node = conn.create_node(name=machine_name, image=image, size=size, location=location) except Exception as e: return Response('Something went wrong with generic node creation: %s' % e, 500) return {'id': node.id, 'name': node.name, 'extra': node.extra, 'public_ips': node.public_ips, 'private_ips': node.private_ips, }
def list_machines(request): """Gets machines and their metadata for a backend. Because each provider stores metadata in different places several checks are needed. The folowing are considered::: * For tags, Rackspace stores them in extra.metadata.tags while EC2 in extra.tags.tags. * For images, both EC2 and Rackpace have an image and an etra.imageId attribute * For flavors, EC2 has an extra.instancetype attribute while Rackspace an extra.flavorId. however we also expect to get size attribute. """ try: conn = connect(request) except RuntimeError as e: log.error(e) return Response('Internal server error: %s' % e, 503) except: return Response('Backend not found', 404) try: machines = conn.list_nodes() except: return Response('Backend unavailable', 503) ret = [] for m in machines: tags = m.extra.get('tags', None) or m.extra.get('metadata', None) tags = tags or {} tags = [value for key, value in tags.iteritems() if key != 'Name'] if m.extra.get('availability', None): # for EC2 tags.append(m.extra['availability']) elif m.extra.get('DATACENTERID', None): # for Linode tags.append(LINODE_DATACENTERS[m.extra['DATACENTERID']]) image_id = m.image or m.extra.get('imageId', None) size = m.size or m.extra.get('flavorId', None) size = size or m.extra.get('instancetype', None) machine = { 'id': m.id, 'uuid': m.get_uuid(), 'name': m.name, 'imageId': image_id, 'size': size, 'state': STATES[m.state], 'private_ips': m.private_ips, 'public_ips': m.public_ips, 'tags': tags, 'extra': m.extra, } machine.update(get_machine_actions(m, conn)) ret.append(machine) return ret
def create_machine(request): """Creates a new virtual machine on the specified backend. If the backend is Rackspace it attempts to deploy the node with an ssh key provided in config. the method used is the only one working in the old Rackspace backend. create_node(), from libcloud.compute.base, with 'auth' kwarg doesn't do the trick. Didn't test if you can upload some ssh related files using the 'ex_files' kwarg from openstack 1.0 driver. In Linode creation is a bit different. There you can pass the key file directly during creation. The Linode API also requires to set a disk size and doesn't get it from size.id. So, send size.disk from the client and use it in all cases just to avoid provider checking. Finally, Linode API does not support association between a machine and the image it came from. We could set this, at least for machines created through mist.io in ex_comment, lroot or lconfig. lroot seems more appropriate. However, liblcoud doesn't support linode.config.list at the moment, so no way to get them. Also, it will create inconsistencies for machines created through mist.io and those from the Linode interface. """ try: conn = connect(request) except: return Response("Backend not found", 404) backend_id = request.matchdict["backend"] try: key_id = request.json_body["key"] except: key_id = None try: keypairs = request.environ["beaker.session"]["keypairs"] except: keypairs = request.registry.settings.get("keypairs", {}) if key_id: keypair = get_keypair_by_name(keypairs, key_id) else: keypair = get_keypair(keypairs) if keypair: private_key = keypair["private"] public_key = keypair["public"] else: private_key = public_key = None try: machine_name = request.json_body["name"] location_id = request.json_body["location"] image_id = request.json_body["image"] size_id = request.json_body["size"] # deploy_script received as unicode, but ScriptDeployment wants str script = str(request.json_body.get("script", "")) # these are required only for Linode, passing them anyway image_extra = request.json_body["image_extra"] disk = request.json_body["disk"] except Exception as e: return Response("Invalid payload", 400) size = NodeSize(size_id, name="", ram="", disk=disk, bandwidth="", price="", driver=conn) image = NodeImage(image_id, name="", extra=image_extra, driver=conn) if conn.type in EC2_PROVIDERS: locations = conn.list_locations() for loc in locations: if loc.id == location_id: location = loc break else: location = NodeLocation(location_id, name="", country="", driver=conn) if conn.type in [Provider.RACKSPACE_FIRST_GEN, Provider.RACKSPACE] and public_key: key = SSHKeyDeployment(str(public_key)) deploy_script = ScriptDeployment(script) msd = MultiStepDeployment([key, deploy_script]) try: node = conn.deploy_node(name=machine_name, image=image, size=size, location=location, deploy=msd) associate_key(request, key_id, backend_id, node.id, deploy=False) except Exception as e: return Response("Failed to create machine in Rackspace: %s" % e, 500) elif conn.type in EC2_PROVIDERS and public_key and private_key: imported_key = import_key(conn, public_key, key_id) created_security_group = create_security_group(conn, EC2_SECURITYGROUP) deploy_script = ScriptDeployment(script) (tmp_key, tmp_key_path) = tempfile.mkstemp() key_fd = os.fdopen(tmp_key, "w+b") key_fd.write(private_key) key_fd.close() # deploy_node wants path for ssh private key if imported_key and created_security_group: try: node = conn.deploy_node( name=machine_name, image=image, size=size, deploy=deploy_script, location=location, ssh_key=tmp_key_path, ssh_alternate_usernames=["ec2-user", "ubuntu"], max_tries=1, ex_keyname=key_id, ex_securitygroup=EC2_SECURITYGROUP["name"], ) associate_key(request, key_id, backend_id, node.id, deploy=False) except Exception as e: return Response("Failed to create machine in EC2: %s" % e, 500) # remove temp file with private key try: os.remove(tmp_key_path) except: pass elif conn.type is Provider.LINODE and public_key and private_key: auth = NodeAuthSSHKey(public_key) (tmp_key, tmp_key_path) = tempfile.mkstemp() key_fd = os.fdopen(tmp_key, "w+b") key_fd.write(private_key) key_fd.close() deploy_script = ScriptDeployment(script) try: node = conn.deploy_node( name=machine_name, image=image, size=size, deploy=deploy_script, location=location, auth=auth, ssh_key=tmp_key_path, ) associate_key(request, key_id, backend_id, node.id, deploy=False) except Exception as e: return Response("Failed to create machine in Linode: %s" % e, 500) # remove temp file with private key try: os.remove(tmp_key_path) except: pass else: return Response("Cannot create a machine without a keypair", 400) return { "id": node.id, "name": node.name, "extra": node.extra, "public_ips": node.public_ips, "private_ips": node.private_ips, }
def list_machines(request): """Gets machines and their metadata from a backend. Several checks are needed, because each backend stores metadata differently. The folowing are considered::: * For tags, Rackspace stores them in extra.metadata.tags while EC2 in extra.tags.tags. * For images, both EC2 and Rackpace have an image and an etra.imageId attribute * For flavors, EC2 has an extra.instancetype attribute while Rackspace an extra.flavorId. however we also expect to get size attribute. """ try: conn = connect(request) except RuntimeError as e: log.error(e) return Response("Internal server error: %s" % e, 503) except: return Response("Backend not found", 404) try: machines = conn.list_nodes() except InvalidCredsError: return Response("Invalid credentials", 401) except: return Response("Backend unavailable", 503) ret = [] for m in machines: tags = m.extra.get("tags", None) or m.extra.get("metadata", None) tags = tags or {} tags = [value for key, value in tags.iteritems() if key != "Name"] if m.extra.get("availability", None): # for EC2 tags.append(m.extra["availability"]) elif m.extra.get("DATACENTERID", None): # for Linode tags.append(LINODE_DATACENTERS[m.extra["DATACENTERID"]]) image_id = m.image or m.extra.get("imageId", None) size = m.size or m.extra.get("flavorId", None) size = size or m.extra.get("instancetype", None) machine = { "id": m.id, "uuid": m.get_uuid(), "name": m.name, "imageId": image_id, "size": size, "state": STATES[m.state], "private_ips": m.private_ips, "public_ips": m.public_ips, "tags": tags, "extra": m.extra, } machine.update(get_machine_actions(m, conn)) ret.append(machine) return ret