def create(self, subnet, mask, start_ip=None, stop_ip=None): """Create a DHCP subnet; return DHCP subnet objects.""" subnet = common.validate_ip_address(subnet) filter = 'cn=%s' % subnet if not common.is_number(mask): msg = 'Subnet mask must be an integer, dotted decimal (or any other\ notation) is not allowed' self.log.error(msg) raise error.InputError(msg) mask = str(mask) if start_ip and not stop_ip: msg = 'A range must include a start and stop IP address' self.log.error(msg) raise error.InputError(msg) if start_ip is not None: start_ip = common.validate_ip_address(start_ip) stop_ip = common.validate_ip_address(stop_ip) start_ip, stop_ip = self._is_greater_ip_than(start_ip, stop_ip) dn = 'cn=%s,%s' % (subnet, self.dhcp_service_dn) dn_attr = { 'objectClass': ['top', self.dhcp_subnet_class], 'cn': [subnet], 'dhcpNetMask': [mask] } if start_ip is not None: dn_attr['dhcpRange'] = start_ip + ' ' + stop_ip dn_info = [(k, v) for (k, v) in dn_attr.items()] result = self._create_object(dn, dn_info) self.log.debug('Result: %s' % result) return result
def is_shell_safe(string): """Ensure input contains no dangerous characters.""" max_length = 64 string = str(string) pattern = re.compile('^[-_A-Za-z0-9 \.]+$') valid = pattern.match(string) if not valid: msg = '%s contains illegal characters' % string raise error.InputError(msg) if len(string) > max_length: msg = '%s cannot be longer than %s' % (string, max_length) raise error.InputError(msg) return string
def validate_domain(domain_name): msg = '%s is not a valid domain name' % domain_name if domain_name is None: raise error.InputError(msg) domain_name = domain_name.lower() if domain_name[-1:] == ".": domain_name = domain_name[:-1] # strip dot from the right if len(domain_name) > 255: raise error.InputError(msg) pattern = re.compile("(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE) for x in domain_name.split("."): valid = pattern.match(x) if not valid: raise error.InputError(msg) return domain_name
def create(self, aloc_ips=None): """Create subnet kv stores; populate with IPs; return True.""" if not (self.network and self.mask): msg = 'Please specify ip and mask' raise error.InputError(msg) if self.KV.exists(self.kv_aloc) or self.KV.exists(self.kv_free): msg = 'Subnet %s/%s already exists' % (self.kv_name, str( self.mask)) raise error.AlreadyExists(msg) self._populate_aloc_ips(aloc_ips) self._populate_free_ips() msg = 'Created subnet %s/%s with %s free and %s reserved IPs' \ % (self.kv_name, str(self.mask), len(self.free_ips), len(self.aloc_ips)) self.log.debug(msg) result = self.get() if result['exit_code'] == 0 and result['count'] == 1: result['msg'] = "Created %s:" % result['type'] return result else: msg = 'Create operation returned OK, but unable to find object' raise error.NotFound(msg) return result
def _get_type_attr(self, type): """Validate resource type; return resource type's LDAP attribute.""" if type in self.dns_type_attrs: return self.dns_type_attrs[type] else: msg = 'Unknown DNS resource type' raise error.InputError(msg)
def create(self, uuid_start=None, get_mac=False): """Create initial UUID object; return True.""" self.log.debug('Running UUID create with get_mac set to %s' % get_mac) if uuid_start: self.next_uuid_start = uuid_start if not common.is_number(self.next_uuid_start): msg = 'next_uuid_start must be an integer' raise error.InputError(msg) # Now we know it's an integer, make it a string for LDAP's sake self.next_uuid_start = str(self.next_uuid_start) dn = self.next_uuid_dn dn_info = [] if not self.next_uuid_class in self.next_uuid_classes: dn_info.append((0, 'objectClass', self.next_uuid_class)) if not self.next_uuid_attr in self.next_uuid_attrs: dn_info.append((0, self.next_uuid_attr, self.next_uuid_start)) if dn_info == []: msg = 'UUID entry already set on %s.' % dn raise error.AlreadyExists(msg) self.log.debug('Adding %s to %s ' % (dn_info, dn)) result = self._create_object(dn, dn_info) uuid = int(result['data'][0][1][self.next_uuid_attr][0]) mac = common.mac_from_uuid(uuid, 0) if get_mac: result['data'] = (uuid, mac) else: result['data'] = [uuid] result['msg'] = 'Created UUID:' self.log.debug('Result: %s' % result) return result
def _validate_number(self, name, number): try: int(number) except: msg = '%s must be an integer, you passed %s' % (name, number) self.log.error(msg) raise error.InputError(msg) return number
def validate_ip_address(ip): """Ensure input is a valid IP address.""" ip = str(ip) pattern = re.compile(r"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$") valid = pattern.match(ip) if not valid: msg = '%s is not a valid IP address' % ip raise error.InputError(msg) return ip
def validate_storage_format(size): """Ensure input is a valid storage value.""" size = str(size) pattern = re.compile('^[0-9]{1,3}[kKmMgG]$') valid = pattern.match(size) if not valid: msg = '%s is not a valid storage format' % size raise error.InputError(msg) return size
def validate_email_address(email_addr): """Ensure input is a valid email address format.""" email_addr = email_addr.lower() pattern = re.compile(r"^[-a-z0-9_.]+@[-a-z0-9]+\.+[a-z]{2,6}") valid_email = pattern.match(email_addr) if not valid_email: msg = '%s is not a valid email address' % email_addr raise error.InputError(msg) return email_addr
def _validate_mailbox_name(self, mailbox_name): """Ensure input is a valid email address format.""" mailbox_name = mailbox_name.lower() pattern = re.compile(r"^[-a-z0-9_.]+@[-a-z0-9]+\.+[a-z]{2,6}") valid_mailbox = pattern.match(mailbox_name) if not valid_mailbox: msg = '%s is not a valid mailbox name' % mailbox_name raise error.InputError(msg) return mailbox_name
def is_shell_safe(string): """Ensure input contains no dangerous characters.""" string = str(string) pattern = re.compile('^[-_A-Za-z0-9 \.]+$') valid = pattern.match(string) if not valid: msg = '%s contains illegal characters' % string raise error.InputError(msg) return string
def validate_host_family(family): if family is None: msg = "Please specify host family; must be xen or kvm or vmware." raise error.InputError, msg pattern = re.compile('^(test|xen|kvm|vmware)$') valid_family = pattern.match(family) if not valid_family: msg = "%s is not a valid host family; must be xen, kvm or vmware" % family raise error.InputError(msg) return family
def _validate_input(self, entry): """Check input and add trailing period to hostname.""" entry_list = entry.split(' ') if len(entry_list) > 2: msg = 'Too many entries: do you have a space somewhere?' self.log.error(msg) raise error.InputError(msg) if len(entry_list) < 2: msg = 'Too few entries: are you missing the priority?' self.log.error(msg) raise error.InputError(msg) priority = entry_list[0] hostname = entry_list[1] try: int(priority) except: msg = 'MX record priority:%s must be an integer.' % priority self.log.error(msg) raise error.InputError(msg) hostname = common.validate_domain(hostname) return priority + ' ' + hostname + '.'
def modify(self, reserve=False, release=False): """Reserve or release IP address from Subnet""" if not (self.network and self.mask): msg = 'Please specify ip and mask' raise error.InputError(msg) if not reserve and not release: msg = 'You must specify if you wish to reserve or release an IP' raise error.InputError(msg) if reserve and release: msg = 'You cannot simultaneously reserve and release an IP' raise error.InputError(msg) if reserve: offer = self._reserve_ip(reserve) result = common.process_results(offer) result['msg'] = 'Reserved ip(s) %s from %s' % (offer, self.kv_name) self.log.debug('Result: %s' % result) return result if release: self._release_ip(release) result = self.get() result['msg'] = 'Returned %s ip(s) to %s' % (release, self.kv_name) self.log.debug('Result: %s' % result) return result
def _is_greater_ip_than(self, start_ip, stop_ip): """Ensure the last ip is greater than the first.""" start_ip_list = start_ip.split('.') stop_ip_list = stop_ip.split('.') index = 0 while index < len(start_ip_list): if start_ip_list[index] != stop_ip_list[index]: # Found non match if int(start_ip_list[index]) > int(stop_ip_list[index]): msg = '%s is greater than %s' % (start_ip, stop_ip) self.log.error(msg) raise error.InputError(msg) # We only care about the first match break index = index + 1 return start_ip, stop_ip
def create(self, host_name): """Create DHCP host; return DHCP host objects.""" filter = 'cn=%s' % host_name if self.dhcp_server == host_name: msg = 'DHCP hostname %s with same name as DHCP server %s' % \ (host_name, self.dhcp_server) self.log.error(msg) raise error.InputError(msg) dn = 'cn=%s,%s' % (host_name, self.dhcp_group_dn) dn_attr = { 'objectClass': ['top', self.dhcp_host_class, self.dhcp_options_class], 'cn': [host_name] } dn_info = [(k, v) for (k, v) in dn_attr.items()] result = self._create_object(dn, dn_info) self.log.debug('Result: %s' % result) return result
def delete(self): """Delete subnet kv stores; return True.""" if not (self.network and self.mask): msg = 'Please specify ip and mask' raise error.InputError(msg) if self.get()['data'] == []: msg = "cannot delete as already missing" raise error.NotFound, msg # Delete kv stores self.KV.delete(self.kv_aloc) self.KV.delete(self.kv_free) result = self.get() if result['exit_code'] == 3 and result['count'] == 0: result['msg'] = "Deleted %s:" % result['type'] return result else: msg = 'Delete operation returned OK, but object still there?' raise error.SearchError(msg)
def __init__(self, ip=None, mask=None, dc=None): """Get config, setup logging and Redis connection.""" SpokeKV.__init__(self) self.config = config.setup() self.log = logger.setup(__name__) self.max_mask = 16 # This is the largest network we can work with # Check our subnet is well formatted in dotted decimal if ip and mask: common.validate_ip_address(ip) # Check our netmask is well formatted and NOT in dotted decimal try: common.validate_ip_address(mask) except: pass # It's not a dotted decimal format subnet, good else: # It validated as a dotted decimal, but we need an integer msg = 'IPv4 subnet mask must be between %s and 32' % self.max_mask raise error.InputError(msg) self.subnet = ip_helper.Network(ip, int(mask)) self.network = self.subnet.network() self.mask = self.subnet.mask if dc is not None: self.kv_name = dc + str(self.network) self.kv_free = '%s:%s:%s:free' % (dc, self.network, self.mask) self.kv_aloc = '%s:%s:%s:aloc' % (dc, self.network, self.mask) else: self.kv_name = str(self.network) self.kv_free = '%s:%s:free' % (self.network, self.mask) self.kv_aloc = '%s:%s:aloc' % (self.network, self.mask) self.ip_ldap_enabled = self.config.get('IP', 'ip_ldap_enabled', False) self.ip_ldap_attr = self.config.get('IP', 'ip_ldap_attr', 'dhcpStatements') self.ip_ldap_key = self.config.get('IP', 'ip_ldap_key', 'fixed-address') self.ip_ldap_search_base = self.config.get('IP', 'ip_ldap_search_base', False) else: (self.network, self.mask) = (None, None)
def modify(self, list_address, enable): """Enable/Disable a mailing list; return True.""" list_address = self._validate_input(list_address) list_info = self.get(list_address) if list_info['data'] == []: msg = 'Unable to modify mailing list access, no list found.' raise error.NotFound(msg) dn = list_info['data'][0].__getitem__(0) old_result = list_info['data'][0].__getitem__(1) old_attrs = {self.list_enable_attr: old_result[self.list_enable_attr]} if enable == True: new_attrs = {self.list_enable_attr: 'TRUE'} elif enable == False: new_attrs = {self.list_enable_attr: 'FALSE'} else: msg = 'enable can only be one of True or False' raise error.InputError(msg) result = self._modify_attributes(dn, new_attrs, old_attrs) self.log.debug('Result: %s' % result) return result
def _populate_free_ips(self): """Populate KV store with all free IP within a given subnet.""" all_ips = set() # /8 network takes 1h 9m (2Ghz CPU and 4GB RAM); produces DB of 220MB if self.mask < self.max_mask: msg = 'Subnets larger than %s must be created manually' % self.mask raise error.InputError(msg) for ip in self.subnet: all_ips.add(str(ip)) self.free_ips = all_ips - self.aloc_ips for free_ip in self.free_ips: self.KV.sadd(self.kv_free, free_ip) # Remove the network and broadcast addresses self.KV.srem(self.kv_free, self.subnet.network()) self.KV.srem(self.kv_free, self.subnet.broadcast()) msg = 'KV store %s populated with %s free IP addresses' % \ (self.kv_free, len(self.free_ips)) self.log.debug(msg) return True
def modify(self, enable): """Modify a user account's repository access; return True.""" svn_info = self.get()['data'] if svn_info == []: msg = 'Unable to modify repository access, no repositories found.' raise error.NotFound(msg) dn = svn_info[0].__getitem__(0) old_result = svn_info[0].__getitem__(1) old_attrs = {self.svn_enable_attr: old_result[self.svn_enable_attr]} if enable == True: new_attrs = {self.svn_enable_attr: 'TRUE'} elif enable == False: new_attrs = {self.svn_enable_attr: 'FALSE'} else: msg = 'enable can only be one of True or False' raise error.InputError(msg) self.log.debug('Modifying %s from %s to %s' % (dn, old_attrs, new_attrs)) result = self._modify_attributes(dn, new_attrs, old_attrs) self.log.debug('Result: %s' % result) return result
def get(self, first=None, last=None, unique=False): """Retrieve a user account; return user object.""" if first is None and last is None: if unique: raise error.InputError('You must target a specific user in \ order to search for a unique value') filter = '%s=*' % self.user_key # Return a list of all users else: self._gen_user_info(first, last) filter = '%s=%s' % (self.user_key, self.user_id) if unique: trueorfalse = True else: trueorfalse = False msg = 'Searching at %s with scope %s and filter %s' % \ (self.org_dn, self.search_scope, filter) self.log.debug(msg) result = self._get_object(self.org_dn, self.search_scope, \ filter, unique=trueorfalse) self.log.debug('Result: %s' % result) return result
def delete(self, vm_name): '''Remove a definition from store, will fail on xen if machine is running''' if vm_name == None: msg = "InputError: vm name must be specified with delete." raise error.InputError(msg) try: dom = self.conn.lookupByName(vm_name) except libvirt.libvirtError: msg = "VM definition for %s doesn't exist, can't delete." % vm_name raise error.NotFound(msg) try: dom.undefine() except libvirt.libvirtError: msg = "VM %s is running (shutdown first?), can't delete." % vm_name raise error.VMRunning(msg) #self.conn.close() result = self.get(vm_name) if result['exit_code'] == 3 and result['count'] == 0: result['msg'] = "Deleted %s:" % result['type'] return result else: msg = 'Delete operation returned OK, but object still there?' raise error.SearchError(msg)
def create(self, vm_name, vm_uuid, vm_mem, vm_cpu, vm_family, vm_storage_layout, vm_network_layout, vm_install=False, vm_disks=None, vm_interfaces=None): """Define a new VM and add to hypervisor's store (does not start).""" try: vm_name = common.validate_hostname(vm_name) vm_cpu = common.validate_cpu(vm_cpu) vm_mem = common.validate_mem(vm_mem) #vm_type = common.validate_host_type(vm_type) vm_family = common.validate_host_family(vm_family) #vm_extra_opts = common.is_shell_safe(vm_extra_opts) vm_uuid = common.validate_uuid(vm_uuid) except error.InputError as e: self.log.error(e) raise e try: self.conn.lookupByName(vm_name) except libvirt.libvirtError: pass else: msg = "Domain %s already exists, cannot create." % vm_name raise error.AlreadyExists(msg) # Create a UUID in hypervisor format formatted_uuid = self._format_uuid(vm_uuid) #-1 means XEN will give the right XenID when it starts vm_id=-1 #Initial xml structure doc = xml.createDoc("doc") domain = xml.createElement(doc, "domain", {"type": vm_family}) #Variable config options #xml.createChild(doc, domain, "arch name", None, 'i686') xml.createChild(doc, domain, "name", None, vm_name) xml.createChild(doc, domain, "memory", None, vm_mem) xml.createChild(doc, domain, "currentMemory", None, vm_mem) xml.createChild(doc, domain, "vcpu", None, vm_cpu) xml.createChild(doc, domain, "uuid", None, formatted_uuid) #fixed(ish) config options os = xml.createChild(doc, domain, "os", None, None) #ks - the below is going to have to change for 64bit xml.createChild(doc, os, "type", {"arch": "i686"}, "hvm") xml.createChild(doc, domain, "clock", {"offset": "utc"}, None) xml.createChild(doc, domain, "on_poweroff", None, "destroy") xml.createChild(doc, domain, "on_reboot", None, "restart") xml.createChild(doc, domain, "on_crash", None, "restart") devices = xml.createChild(doc, domain, "devices", None, None) console = xml.createChild(doc, devices, "console", {"type": "pty"}, None) xml.createChild(doc, console, "target", {"type": "xen", "port": "0"}, None) #ks #xml.createChild(doc, devices, "input", {"type": "mouse", "bus": "xen"}, None) # TODO Change the port such that it is associated with the UUID and change listen to internal interface only xml.createChild(doc, devices, "graphics", {"type": "vnc", "port": "-1", "autoport": "yes", "listen": "0.0.0.0"}, None) xml.createChild(doc, devices, "emulator", None, "/usr/lib/xen/bin/qemu-dm") # #parse disk info # for item in vm_disks: # #local1 means hda and vg01 # if item[0] == "local1": # disk = xml.createChild(doc, devices, "disk", {"type": "block", "device": "disk"}, None) # xml.createChild(doc, disk, "driver", {"name": "phy"}, None) # xml.createChild(doc, disk, "source", {"dev": "/dev/vg01/%s" % vm_name}, None) # xml.createChild(doc, disk, "target", {"dev": "hda", "bus": "ide"}, None) # #local2 means hdb and vg02 # if item[0] == "local2": # disk = xml.createChild(doc, devices, "disk", {"type": "block", "device": "disk"}, None) # xml.createChild(doc, disk, "driver", {"name": "phy"}, None) # xml.createChild(doc, disk, "source", {"dev": "/dev/vg01/ko-test-02"}, None) # xml.createChild(doc, disk, "target", {"dev": "hdb", "bus": "ide"}, None) if vm_disks is not None: for item in vm_disks: if item[0] == "local1": disk = xml.createChild(doc, devices, "disk", {"type": "block", "device": "disk"}, None) xml.createChild(doc, disk, "driver", {"name": "phy"}, None) xml.createChild(doc, disk, "source", {"dev": "/dev/vg01/%s" % vm_name}, None) xml.createChild(doc, disk, "target", {"dev": "hda", "bus": "ide"}, None) #local2 means hdb and vg02 if item[0] == "local2": disk = xml.createChild(doc, devices, "disk", {"type": "block", "device": "disk"}, None) xml.createChild(doc, disk, "driver", {"name": "phy"}, None) xml.createChild(doc, disk, "source", {"dev": "/dev/vg02/%s" % vm_name}, None) xml.createChild(doc, disk, "target", {"dev": "hdb", "bus": "ide"}, None) elif vm_storage_layout is not None: try: disks = self.dtypes[vm_storage_layout] except KeyError as e: msg = "The disk type %s is not present in config file." % e raise error.InputError, msg for item in disks: item = common.validate_disks_in_conf(self.dnames[item]) hv_dev = item[0] + "/" + vm_name dom_dev = item[1] disk = xml.createChild(doc, devices, "disk", {"type": "block", "device": "disk"}, None) xml.createChild(doc, disk, "driver", {"name": "phy"}, None) xml.createChild(doc, disk, "source", {"dev": hv_dev}, None) xml.createChild(doc, disk, "target", {"dev": dom_dev, "bus": "ide"}, None) #parse interface info if vm_interfaces is not None: for interface in vm_interfaces: #get input from interface list bridge = int( interface[0].lstrip('breth') ) mac = interface[1] source_interface = interface[2] interface = xml.createChild(doc, devices, "interface", {"type": "bridge"}, None) xml.createChild(doc, interface, "mac", {"address": mac}, None) xml.createChild(doc, interface, "source", {"bridge": source_interface}, None) xml.createChild(doc, interface, "script", {"path": "vif-bridge"}, None) xml.createChild(doc, interface, "target", {"dev": "vif%i.%i" % (vm_id, bridge)}, None) elif vm_network_layout is not None: try: interfaces = self.ifacedef[vm_network_layout] except KeyError: msg = "The interface type %s is not present in config file." % vm_network_layout raise error.InputError(msg) # Ensure that br0,x is done first as xen cares about order in xml. interfaces = sorted(interfaces, key=itemgetter(0)) for interface in interfaces: interface = common.validate_interfaces_in_conf(interface) iface_number = int( interface[0].lstrip('breth') ) if iface_number == 0: boot_mac = common.mac_from_uuid(vm_uuid, iface_number) boot_int = interface[1] mac = common.mac_from_uuid(vm_uuid, iface_number) source_interface = interface[1] # KS enumerate avail interfaces via facter, not remote socket op #if not source_interface in self._all_interfaces(): # msg = "%s does not exist on this machine so we cant bridge to it!" % source_interface # raise error.InsufficientResource, msg interface = xml.createChild(doc, devices, "interface", {"type": "bridge"}, None) xml.createChild(doc, interface, "mac", {"address": mac}, None) xml.createChild(doc, interface, "source", {"bridge": source_interface}, None) xml.createChild(doc, interface, "script", {"path": "vif-bridge"}, None) xml.createChild(doc, interface, "target", {"dev": "vif%i.%i" % (vm_id, iface_number)}, None) if vm_install: # Add network boot lines xml.createChild(doc, domain, "bootloader", None, "/usr/sbin/pypxeboot" ) try: xml.createChild(doc, domain, "bootloader_args", None, "--udhcpc=/usr/local/pkg/udhcp/sbin/udhcpc --interface=%s mac=%s --label=install-aethernet" % (boot_int, boot_mac) ) except UnboundLocalError: msg = "In config there must be an interface br0 as the provisioning interface!" raise error.ConfigError(msg) else: xml.createChild(doc, domain, "bootloader", None, "/usr/bin/pygrub" ) try: out = self.conn.defineXML(xml.out(doc)) except Exception, e: trace = traceback.format_exc() raise error.LibvirtError(e, trace)