class Nodes(object): """ UMC functions for UVMM node handling. """ @sanitize(nodePattern=SearchSanitizer(default='*')) def node_query(self, request): """ Searches nodes by the given pattern options: {'nodePattern': <pattern>} return: [{ 'id': <node URI>, 'label': <node name>, 'group': 'default', 'type': 'node', 'virtech': <virtualization technology>, 'memUsed': <used amount of memory in B>, 'memAvailable': <amount of physical memory in B>, 'cpus': <number of CPUs>, 'cpuUsage': <cpu usage in %>, 'available': (True|False), 'supports_suspend': (True|False), 'supports_snapshot': (True|False) }, ...] """ def _finished(data): """ Process asynchronous UVMM NODE_LIST answer. """ nodes = [] for node_pd in data: node_uri = urlsplit(node_pd.uri) nodes.append({ 'id': node_pd.uri, 'label': node_pd.name, 'group': _('Physical servers'), 'type': 'node', 'virtech': node_uri.scheme, 'memUsed': node_pd.curMem, 'memAvailable': node_pd.phyMem, 'cpuUsage': (node_pd.cpu_usage or 0) / 10.0, 'available': node_pd.last_try == node_pd.last_update, 'cpus': node_pd.cpus, 'supports_suspend': node_pd.supports_suspend, 'supports_snapshot': node_pd.supports_snapshot, }) return nodes self.uvmm.send('NODE_LIST', self.process_uvmm_response(request, _finished), group='default', pattern=request.options['nodePattern'])
class Cloud(object): """ Handle cloud connections and instances. """ @sanitize(nodePattern=SearchSanitizer(default='*')) def cloud_query(self, request): """ Searches clouds by the given pattern options: {'nodePattern': <cloud pattern>} return: [{ 'id': <cloud name>, 'label': <cloud name>, 'group': 'cloudconnection', 'type': 'cloud', 'available': (True|False), }, ...] """ self.required_options(request, 'nodePattern') def _finished(data): """ Process asynchronous UVMM L_CLOUD_LIST answer. """ return [{ 'id': d.name, 'label': d.name, 'group': _('Cloud connection'), 'type': 'cloud', 'cloudtype': d.cloudtype, 'available': d.available, 'last_error_message': d.last_error_message, 'dn': d.dn, 'search_pattern': d.search_pattern, 'ucs_images': d.ucs_images, } for d in data] self.uvmm.send('L_CLOUD_LIST', self.process_uvmm_response(request, _finished), pattern=request.options['nodePattern']) def cloud_add(self, request): """ Add a new cloud connection into ldap. options: { ['cloudtype': <uvmm/cloudtype>,] ['name': <new cloud name>,] ['parameter': <key/value parameter>,] ['testconnection': true (default) / false,] } return: [] """ def _finished(data): # add cloud to ldap ldap_cloud_connection_add(cloudtype, name, parameter, ucs_images, search_pattern, preselected_images) return data self.required_options(request, 'cloudtype', 'name', 'parameter', 'testconnection') cloudtype = request.options.get('cloudtype') name = request.options.get('name') testconnection = request.options.get('testconnection') parameter = request.options.get('parameter', {}) search_pattern = parameter.pop('search_pattern', '') preselected_images = parameter.pop('preselected_images', []) ucs_images = parameter.pop('ucs_images', True) # add cloud to uvmm args = parameter.copy() args['name'] = name args['type'] = cloudtype args['search_pattern'] = search_pattern args['preselected_images'] = preselected_images args['ucs_images'] = ucs_images self.uvmm.send('L_CLOUD_ADD', self.process_uvmm_response(request, _finished), args=args, testconnection=testconnection) def cloud_list_keypair(self, request): """ Returns a list of keypair for the given cloud conn_name. """ self.required_options(request, 'conn_name') conn_name = request.options.get('conn_name') def _finished(data): """ Process asynchronous UVMM L_CLOUD_KEYPAIR_LIST answer. """ return [{ 'id': item.name, 'label': item.name } for conn_name, images in data.items() for item in images] self.uvmm.send('L_CLOUD_KEYPAIR_LIST', self.process_uvmm_response(request, _finished), conn_name=conn_name) def cloud_list_size(self, request): """ Returns a list of hardware sizes for the given cloud conn_name. """ self.required_options(request, 'conn_name') conn_name = request.options.get('conn_name') def _finished(data): """ Process asynchronous UVMM L_CLOUD_SIZE_LIST answer. """ size_list = [] for conn_name, images in data.items(): for item in images: size_list.append({ 'id': item.id, 'label': item.u_displayname, 'disk': item.disk, 'ram': item.ram, 'vcpus': item.vcpus, }) return size_list self.uvmm.send('L_CLOUD_SIZE_LIST', self.process_uvmm_response(request, _finished), conn_name=conn_name) @sanitize(pattern=SearchSanitizer(default='*')) def cloud_list_image(self, request): """ Returns a list of images by a pattern for the given cloud conn_name. """ self.required_options(request, 'conn_name') conn_name = request.options.get('conn_name') def _finished(data): """ Process asynchronous UVMM L_CLOUD_IMAGE_LIST answer. """ return [{ 'id': item.id, 'label': item.name } for conn_name, images in data.items() for item in images] self.uvmm.send('L_CLOUD_IMAGE_LIST', self.process_uvmm_response(request, _finished), conn_name=conn_name) def cloud_list_secgroup(self, request): """ Returns a list of security groups for the given cloud conn_name. """ self.required_options(request, 'conn_name') conn_name = request.options.get('conn_name') network_id = request.options.get('network_id') def _finished(data): """ Process asynchronous UVMM L_CLOUD_SECGROUP_LIST answer. """ return [{ 'id': item.id, 'label': item.name } for conn_name, images in data.items() for item in images if network_id in ('default', item.network_id)] self.uvmm.send('L_CLOUD_SECGROUP_LIST', self.process_uvmm_response(request, _finished), conn_name=conn_name) def cloud_list_network(self, request): """ Returns a list of networks for the given cloud conn_name. """ self.required_options(request, 'conn_name') conn_name = request.options.get('conn_name') def _finished(data): """ Process asynchronous UVMM L_CLOUD_NETWORK_LIST answer. """ return [{ 'id': item.id, 'label': '%s %s' % (item.name, item.cidr or "") } for conn_name, images in data.items() for item in images] self.uvmm.send('L_CLOUD_NETWORK_LIST', self.process_uvmm_response(request, _finished), conn_name=conn_name) def cloud_list_subnet(self, request): """ Returns a list of subnet for the given cloud conn_name. """ self.required_options(request, 'conn_name') conn_name = request.options.get('conn_name') network_id = request.options.get('network_id') def _finished(data): """ Process asynchronous UVMM L_CLOUD_SUBNET_LIST answer. """ return [{ 'id': item.id, 'label': '%s %s' % (item.name, item.cidr or "") } for conn_name, images in data.items() for item in images if network_id == item.network_id] self.uvmm.send('L_CLOUD_SUBNET_LIST', self.process_uvmm_response(request, _finished), conn_name=conn_name) @sanitize(domainPattern=SearchSanitizer(default='*')) def instance_query(self, request): """ Returns a list of instances matching domainPattern on the clouds matching nodePattern. options: { ['nodePattern': <cloud pattern>,] ['domainPattern': <instance pattern>,] } return: [{ 'node_available': True, 'extra': { 'key_name': None, 'disk_config': 'MANUAL', 'flavorId': '1', 'availability_zone': 'nova', 'password': None, 'metadata': {} }, 'label': 'automagic-997898', 'type': 'instance', 'id': 'myCloud2#e2c8e274-2e17-499c-a3f9-620fb249578c', 'nodeName': 'myCloud2' }, ... ] """ def _finished(data): """ Process asynchronous UVMM L_CLOUD_INSTANCE_LIST answer. """ instances = [] for conn_name, insts in data.items(): for inst in insts: instance_uri = '%s#%s' % (conn_name, inst.id) instances.append({ 'id': instance_uri, 'label': inst.name, 'nodeName': conn_name, 'state': inst.state, 'type': 'instance', 'suspended': None, # FIXME 'description': '%s [%s]' % (inst.u_size_name, inst.state), 'node_available': inst.available, 'extra': inst.extra, 'public_ips': inst.public_ips, 'private_ips': inst.private_ips, 'u_size_name': inst.u_size_name, 'u_connection_type': inst.u_connection_type, 'keypair': inst.key_name, 'image': inst.u_image_name, 'securitygroup': inst.secgroups, }) return instances self.uvmm.send('L_CLOUD_INSTANCE_LIST', self.process_uvmm_response(request, _finished), conn_name=request.options.get('nodePattern', ''), pattern=request.options['domainPattern']) @sanitize(state=ChoicesSanitizer(choices=('RUN', 'RESTART', 'SOFTRESTART', 'SHUTOFF', 'SHUTDOWN', 'SUSPEND', 'PAUSE', 'RESUME', 'UNPAUSE'))) def instance_state(self, request): """ Set the state a instance instance_id on cloud conn_name. options: { 'uri': <conn_name#instance_id>, 'state': (RUN|RESTART|SOFTRESTART|SHUTOFF|SHUTDOWN|SUSPEND|RESUME|UNPAUSE), } return: """ self.required_options(request, 'uri', 'state') conn_name, instance_id = urldefrag(request.options['uri']) state = request.options['state'] self.uvmm.send( 'L_CLOUD_INSTANCE_STATE', self.process_uvmm_response(request), conn_name=conn_name, instance_id=instance_id, state=state, ) def instance_remove(self, request): """ Removes a instance. options: { 'domainURI': <domain uri> } return: """ self.required_options(request, 'domainURI') conn_name, instance_id = urldefrag(request.options['domainURI']) self.uvmm.send('L_CLOUD_INSTANCE_TERMINATE', self.process_uvmm_response(request), conn_name=conn_name, instance_id=instance_id) def instance_add(self, request): """ Create a new instance on cloud conn_name. options: { 'conn_name': <cloud connection name>, 'parameter': {...}, } return: """ self.required_options(request, 'conn_name', 'name', 'parameter') conn_name = request.options.get('conn_name') name = request.options.get('name') parameter = request.options.get('parameter') args = parameter args['name'] = name args['security_group_ids'] = [parameter['security_group_ids']] self.uvmm.send('L_CLOUD_INSTANCE_CREATE', self.process_uvmm_response(request), conn_name=conn_name, args=args) def cloudtype_get(self, request): """ Returns a list of all cloudtypes from ldap. """ cloudtypes = [] for item in ldap_cloud_types(): cloudtypes.append({'id': item['name'], 'label': item['name']}) self.finished(request.id, cloudtypes)
class Domains(object): """ UMC functions for UVMM domain handling. """ STATES = ('NOSTATE', 'RUNNING', 'IDLE', 'PAUSED', 'SHUTDOWN', 'SHUTOFF', 'CRASHED') TARGET_STATES = ('RUN', 'PAUSE', 'SHUTDOWN', 'SHUTOFF', 'RESTART', 'SUSPEND') RE_VNC = re.compile(r'^(IPv[46])(?: (.+))?$|^(?:NAME(?: (.+=.*))?)$') SOCKET_FAMILIES = { 'IPv4': socket.AF_INET, 'IPv6': socket.AF_INET6, } SOCKET_FORMATS = { socket.AF_INET: '%s', socket.AF_INET6: '[%s]', } @sanitize(domainPattern=SearchSanitizer(default='*')) def domain_query(self, request): """ Returns a list of domains matching domainPattern on the nodes matching nodePattern. options: { ['nodePattern': <node name pattern>,] ['domainPattern': <domain pattern>,] } return: [{ 'cpuUsage': <float>, 'description': <string>, 'id': <domain uri>, 'label': <name>, 'mem': <ram>, 'nodeName': <node>, 'node_available': <boolean>, 'state': <state>, 'reason': <reason>, 'suspended': <boolean>, 'type': 'domain', 'vnc': <boolean>, 'vnc_port': <int>, 'migration': <dict>, }, ...] """ def _finished(data): """ Process asynchronous UVMM DOMAIN_LIST answer. """ domain_list = [] for node_uri, domains in data.items(): uri = urlsplit(node_uri) for domain in domains: domain_uri = '%s#%s' % (node_uri, domain['uuid']) domain_list.append({ 'id': domain_uri, 'label': domain['name'], 'nodeName': uri.netloc, 'state': domain['state'], 'reason': domain['reason'], 'type': 'domain', 'mem': domain['mem'], 'cpuUsage': domain['cpu_usage'], 'vnc': domain['vnc'], 'vnc_port': domain['vnc_port'], 'suspended': bool(domain['suspended']), 'description': domain['description'], 'node_available': domain['node_available'], 'error': domain['error'], 'migration': domain['migration'], }) return domain_list self.uvmm.send('DOMAIN_LIST', self.process_uvmm_response(request, _finished), uri=request.options.get('nodePattern', ''), pattern=request.options['domainPattern']) def domain_get(self, request): """ Returns details about a domain domainUUID. options: {'domainURI': <domain uri>} return: {...} """ def _finished(data): """ Process asynchronous UVMM DOMAIN_INFO answer. Convert UVMM protocol to JSON. """ node_uri = urlsplit(request.options['domainURI']) uri, _uuid = urldefrag(request.options['domainURI']) json = object2dict(data) # re-arrange a few attributes for the frontend # annotations for key in json['annotations']: if key == 'uuid': continue json[key] = json['annotations'][key] # STOP here if domain is not available if not json['available']: MODULE.info('Domain is not available: %s' % (json, )) self.finished(request.id, json) return # interfaces (fake the special type network:<source>) for iface in json['interfaces']: if iface['type'] == Interface.TYPE_NETWORK: iface['type'] = 'network:' + iface['source'] # disks for disk in json['disks']: disk['volumeFilename'] = os.path.basename( disk['source']) if disk['pool'] else disk['source'] disk['paravirtual'] = disk['target_bus'] in ('virtio', ) disk['volumeType'] = disk['type'] # graphics if json['graphics']: try: gfx = json['graphics'][0] json['vnc'] = True json['vnc_host'] = None json['vnc_port'] = None json['kblayout'] = gfx['keymap'] json['vnc_remote'] = gfx['listen'] == '0.0.0.0' json['vnc_password'] = gfx['passwd'] # vnc_password will not be send to frontend port = int(json['graphics'][0]['port']) if port == -1: raise ValueError(json['graphics'][0]['port']) host = node_uri.netloc vnc_link_format = ucr.get('uvmm/umc/vnc/host', 'IPv4') or '' match = Domains.RE_VNC.match(vnc_link_format) if match: family, pattern, substs = match.groups() if family: # IPvX family = Domains.SOCKET_FAMILIES[family] regex = re.compile(pattern or '.*') addrs = socket.getaddrinfo(host, port, family, socket.SOCK_STREAM, socket.SOL_TCP) for (family, _socktype, _proto, _canonname, sockaddr) in addrs: host, port = sockaddr[:2] if regex.search(host): break else: raise LookupError(pattern) host = Domains.SOCKET_FORMATS[family] % (host, ) elif substs: # NAME for subst in substs.split(): old, new = subst.split('=', 1) host = host.replace(old, new) elif vnc_link_format: # overwrite all hosts with fixed host host = vnc_link_format json['vnc_host'] = host json['vnc_port'] = port except re.error as ex: # port is not valid MODULE.warn('Invalid VNC regex: %s' % (ex, )) except socket.gaierror as ex: MODULE.warn('Invalid VNC host: %s' % (ex, )) except (ValueError, LookupError) as ex: # port is not valid MODULE.warn('Failed VNC lookup: %s' % (ex, )) # profile (MUST be after mapping annotations) profile_dn = json.get('profile') profile = None if profile_dn: for dn, pro in self.profiles: if dn == profile_dn: profile = pro break if profile: json['profileData'] = object2dict(profile) MODULE.info('Got domain description: %s' % (json, )) return json self.required_options(request, 'domainURI') node_uri, domain_uuid = urldefrag(request.options['domainURI']) self.uvmm.send('DOMAIN_INFO', self.process_uvmm_response(request, _finished), uri=node_uri, domain=domain_uuid) def _create_disk(self, node_uri, disk, domain_info, profile=None): """ Convert single disk from JSON to Python UVMM Disk object. """ uri = urlsplit(node_uri) driver_pv = disk.get('paravirtual', False) # by default no paravirtual devices drive = Disk() drive.device = disk['device'] drive.driver_type = disk['driver_type'] drive.driver_cache = disk.get('driver_cache', 'default') drive.driver = disk.get('driver', None) drive.target_bus = disk.get('target_bus', 'ide') drive.target_dev = disk.get('target_dev', None) pool_name = disk.get('pool') if pool_name: pool = self.get_pool(node_uri, pool_name) else: pool = {} if disk.get('source', None) is None: # new drive drive.size = disk['size'] if not pool: raise ValueError('Pool "%s" not found' % (pool_name, )) drive.source = os.path.join(pool['path'], disk['volumeFilename']) if profile: if drive.device == Disk.DEVICE_DISK: driver_pv = getattr(profile, 'pvdisk', False) elif drive.device == Disk.DEVICE_CDROM: driver_pv = getattr(profile, 'pvcdrom', False) else: # old drive drive.source = disk['source'] MODULE.info('Creating a %s drive' % ('paravirtual' if driver_pv else 'emulated', )) try: pool_type = pool['type'] drive.type = POOLS_TYPE[pool_type] except LookupError: if drive.source.startswith('/dev/'): drive.type = Disk.TYPE_BLOCK elif not drive.source: # empty CDROM or floppy device drive.type = Disk.TYPE_BLOCK else: drive.type = Disk.TYPE_FILE if drive.device == Disk.DEVICE_DISK: drive.readonly = disk.get('readonly', False) elif drive.device == Disk.DEVICE_CDROM: drive.driver_type = 'raw' # ISOs need driver/@type='raw' drive.readonly = disk.get('readonly', True) elif drive.device == Disk.DEVICE_FLOPPY: drive.readonly = disk.get('readonly', True) else: raise ValueError('Invalid drive-type "%s"' % drive.device) if uri.scheme.startswith('qemu'): drive.driver = 'qemu' if drive.device == Disk.DEVICE_FLOPPY: drive.target_bus = 'fdc' elif driver_pv: drive.target_bus = 'virtio' elif disk.get('paravirtual', None) is False: drive.target_bus = 'ide' else: pass # keep else: raise ValueError('Unknown virt-tech "%s"' % (node_uri, )) return drive def domain_add(self, request): """ Creates a new domain on nodeURI. options: { 'nodeURI': <node uri>, 'domain': {...}, } return: """ self.required_options(request, 'nodeURI', 'domain') domain = request.options.get('domain') domain_info = Data_Domain() # when we edit a domain there must be a UUID if 'domainURI' in domain: _node_uri, domain_uuid = urldefrag(domain['domainURI']) domain_info.uuid = domain_uuid # annotations & profile profile = None if not domain_info.uuid: profile_dn = domain.get('profile') for dn, pro in self.profiles: if dn == profile_dn: profile = pro break else: raise UMC_Error(_('Unknown profile given')) domain_info.annotations['profile'] = profile_dn MODULE.info('Creating new domain using profile %s' % (object2dict(profile), )) domain_info.annotations['os'] = getattr(profile, 'os') else: domain_info.annotations['os'] = domain.get('os', '') domain_info.annotations['contact'] = domain.get('contact', '') domain_info.annotations['description'] = domain.get('description', '') domain_info.name = domain['name'] if 'arch' in domain: domain_info.arch = domain['arch'] elif profile: domain_info.arch = profile.arch else: raise UMC_Error(_('Could not determine architecture for domain'), status=500) if domain_info.arch == 'automatic': success, node_list = self.uvmm.send( 'NODE_LIST', None, group='default', pattern=request.options['nodeURI']) if not success: raise UMC_Error( _('Failed to retrieve details for the server %(nodeURI)s') % request.options, status=500) if not node_list: raise UMC_Error(_('Unknown physical server %(nodeURI)s') % request.options, status=500) archs = set([t.arch for t in node_list[0].capabilities]) if 'x86_64' in archs: domain_info.arch = 'x86_64' else: domain_info.arch = 'i686' domain_info.domain_type = 'kvm' domain_info.os_type = 'hvm' domain_info.maxMem = domain['maxMem'] # CPUs try: domain_info.vcpus = int(domain['vcpus']) except ValueError: raise UMC_Error(_('vcpus must be a number')) domain_info.hyperv = domain.get('hyperv', True) # boot devices if 'boot' in domain: domain_info.boot = domain['boot'] elif profile: domain_info.boot = getattr(profile, 'bootdev', None) else: raise UMC_Error( _('Could not determine the list of boot devices for domain'), status=500) # VNC if domain['vnc']: gfx = Graphic() if domain.get('vnc_remote', False): gfx.listen = '0.0.0.0' else: gfx.listen = None if 'kblayout' in domain: gfx.keymap = domain['kblayout'] elif profile: gfx.keymap = profile.kblayout else: raise UMC_Error(_( 'Could not determine the keyboard layout for the VNC access' ), status=500) if domain.get('vnc_password', None): gfx.passwd = domain['vnc_password'] domain_info.graphics = [gfx] # RTC offset if 'rtc_offset' in domain: domain_info.rtc_offset = domain['rtc_offset'] elif profile and getattr(profile, 'rtcoffset'): domain_info.rtc_offset = profile.rtcoffset else: domain_info.rtc_offset = 'utc' # drives domain_info.disks = [ self._create_disk(request.options['nodeURI'], disk, domain_info, profile) for disk in domain['disks'] ] # network interface domain_info.interfaces = [] for interface in domain['interfaces']: iface = Interface() if interface.get('type', '').startswith('network:'): iface.type = 'network' iface.source = interface['type'].split(':', 1)[1] else: iface.type = interface.get('type', 'bridge') iface.source = interface['source'] iface.model = interface['model'] iface.mac_address = interface.get('mac_address', None) domain_info.interfaces.append(iface) def _finished(data): """ Process asynchronous UVMM DOMAIN_DEFINE answer. """ return object2dict(data) self.uvmm.send('DOMAIN_DEFINE', self.process_uvmm_response(request, _finished), uri=request.options['nodeURI'], domain=domain_info) domain_put = domain_add def domain_state(self, request): """ Set the state a domain domainUUID on node nodeURI. options: { 'domainURI': <domain uri>, 'domainState': (RUN|SHUTDOWN|SHUTOFF|PAUSE|RESTART|SUSPEND), } return: """ self.required_options(request, 'domainURI', 'domainState') node_uri, domain_uuid = urldefrag(request.options['domainURI']) MODULE.info('nodeURI: %s, domainUUID: %s' % (node_uri, domain_uuid)) state = request.options['domainState'] if state not in self.TARGET_STATES: raise UMC_Error(_('Invalid domain state: %s') % state) self.uvmm.send( 'DOMAIN_STATE', self.process_uvmm_response(request), uri=node_uri, domain=domain_uuid, state=state, ) def domain_migrate(self, request): """ Migrates a domain from sourceURI to targetURI. options: { 'domainURI': <domain uri>, 'targetNodeURI': <target node uri>, } return: """ self.required_options(request, 'domainURI') node_uri, domain_uuid = urldefrag(request.options['domainURI']) mode = request.options.get('mode', 0) if mode < 101 and mode > -1: self.required_options(request, 'targetNodeURI') target_uri = request.options.get('targetNodeURI', '') self.uvmm.send('DOMAIN_MIGRATE', self.process_uvmm_response(request), uri=node_uri, domain=domain_uuid, target_uri=target_uri, mode=mode) def domain_clone(self, request): """ Clones an existing domain. options: { 'domainURI': <domain uri>, 'cloneName': <name of clone>, 'macAddress' : (clone|auto), } return: """ self.required_options(request, 'domainURI', 'cloneName') node_uri, domain_uuid = urldefrag(request.options['domainURI']) self.uvmm.send( 'DOMAIN_CLONE', self.process_uvmm_response(request), uri=node_uri, domain=domain_uuid, name=request.options['cloneName'], subst={'mac': request.options.get('macAddress', 'clone')}) def domain_remove(self, request): """ Removes a domain. Optional a list of volumes can be specified that should be removed. options: { 'domainURI': <domain uri>, 'volumes': [<filename>...] } return: """ self.required_options(request, 'domainURI', 'volumes') node_uri, domain_uuid = urldefrag(request.options['domainURI']) volume_list = request.options['volumes'] self.uvmm.send('DOMAIN_UNDEFINE', self.process_uvmm_response(request), uri=node_uri, domain=domain_uuid, volumes=volume_list)