def _wait_after_reboot(self, essential): if essential: ssh = SSHInterface(address=self.address, port=self.options.ssh_port) else: ssh = SSHInterface(address=self.address, username=self.options.root_username, password=self.options.root_password, port=self.options.ssh_port) timeout = self.options.timeout try: SCMD.ssh.GetPrompt(ifc=ssh).\ run_wait(lambda x: x not in ('INOPERATIVE', '!'), timeout=timeout, timeout_message="Timeout ({0}s) waiting for a non-inoperative prompt.") SCMD.ssh.FileExists('/var/run/mcpd.pid', ifc=ssh).\ run_wait(lambda x: x, progress_cb=lambda x: 'mcpd not up...', timeout=timeout) SCMD.ssh.FileExists('/var/run/mprov.pid', ifc=ssh).\ run_wait(lambda x: x is False, progress_cb=lambda x: 'mprov still running...', timeout=timeout) SCMD.ssh.FileExists('/var/run/grub.conf.lock', ifc=ssh).\ run_wait(lambda x: x is False, progress_cb=lambda x: 'grub.lock still running...', timeout=timeout) version = SCMD.ssh.get_version(ifc=ssh) finally: ssh.close() return version
class UcsTool(Macro): def __init__(self, options, address=None, ucs_name=None): self.options = Options(options) self.ucs_name = ucs_name self.sshparams = Options(device=self.options.device, address=address, timeout=self.options.timeout, username=self.options.username, password=self.options.password) super(UcsTool, self).__init__() def prep(self): self.sshifc = SSHInterface(**self.sshparams) self.api = self.sshifc.open() self.api.run('rm -rf {0} && mkdir -p {0}'.format(UCS_TMP)) def cleanup(self): self.sshifc.close() def setup(self): if not self.api.exists('/var/local/ucs/{}.ucs'.format(self.ucs_name)): raise RuntimeError('UCS not found on the target.') LOG.info('Processing...') self.api.run(r'cd {} && tar xf /var/local/ucs/{}.ucs'.format(UCS_TMP, self.ucs_name)) # Replace all encrypted secrets which will fail to load anyway. self.api.run(r"sed -ibak '/^\s*#/!s/secret.*$/secret default/g' {}/config/bigip.conf".format(UCS_TMP)) # Adding the license back self.api.run(r"cp -f /config/bigip.license {}/config/".format(UCS_TMP)) # Preserve: # - management IP # - management route # Note: DHCP is not supported text = self.api.sftp().open('/config/bigip_base.conf').read() config = tmsh.parser(text) with self.api.sftp().open('{}/config/bigip_base.conf'.format(UCS_TMP), 'at+') as f: f.write(config.match('sys management-ip .*').dumps()) f.write(config.match('sys management-route /Common/default').dumps()) bit = config.match('sys global-settings') bit['sys global-settings']['mgmt-dhcp'] = 'disabled' bit['sys global-settings']['gui-security-banner-text'] = 'Welcome to the BIG-IP Configuration Utility. Configuration imported by UCS Tool 1.0' f.write(bit.dumps()) self.api.run(r'tar -C {} --xform s:"^./":: -czf /var/local/ucs/{}_imported.ucs .'.format(UCS_TMP, self.ucs_name)) #print config if self.options.load: LOG.info('Loading UCS...') self.api.run('tmsh load sys ucs {}_imported no-platform-check no-license'.format(self.ucs_name)) LOG.info('Done.')
class Tool(Macro): def __init__(self, options, address=None, filename=None): self.options = Options(options) self.filename = filename self.sshparams = Options(device=self.options.device, address=address, timeout=self.options.timeout, username=self.options.username, password=self.options.password, port=self.options.port) super(Tool, self).__init__() def prep(self): self.sshifc = SSHInterface(**self.sshparams) self.api = self.sshifc.open() def cleanup(self): self.sshifc.close() def setup(self): if not self.api.exists(self.filename): raise RuntimeError('File %s not found on the target.' % self.filename) LOG.info('Processing...') with self.api.sftp().open(self.filename) as f: result = parser(f.read()) shell = self.api.interactive() shell.expect_exact(PROMPT) shell.sendline('tmsh') shell.expect_exact(PROMPT) leftovers = set() for path in reversed(result.keys()): if path.startswith('ltm pool'): for member in result[path]['members'].keys(): if member.count('.') == 1: # 2002::1.http leftovers.add('ltm node {}'.format(member.split('.')[0])) elif member.count(':') == 1: # 1.1.1.1:21 leftovers.add('ltm node {}'.format(member.split(':')[0])) else: raise ValueError(member) shell.sendline(TMSH_DELETE.format(path)) shell.expect_exact(PROMPT) LOG.info(shell.before) for path in leftovers: shell.sendline(TMSH_DELETE.format(path)) shell.expect_exact(PROMPT) LOG.info(shell.before) LOG.info('Done.')
def wait_after_reboot(essential, address, ssh_port, root_username, root_password, timeout, interval): """ Wait for the device to be in a valid state after rebooting. The reboot command is assumed to have already been sent. @param essential: If True, this is an essential config case. @param address: IP address of the device. @param ssh_port: Port to use for SSH communications. @param root_username: Name of the root user. @param root_password: Password for the root user. @param timeout: Timeout value (in seconds) to wait for the reboot to be complete. An exception will be thrown if all operations are not completed in this time. @param interval: The frequency (in seconds) of how often the status is checked. @return: The new version information of the device after rebooting. """ if essential: ssh = SSHInterface(address=address, port=ssh_port, username=ROOT_USERNAME, password=ROOT_PASSWORD) else: ssh = SSHInterface(address=address, username=root_username, password=root_password, port=ssh_port) try: SCMD.ssh.GetPrompt(ifc=ssh).\ run_wait(lambda x: x not in ('INOPERATIVE', '!'), timeout=timeout, interval=interval, timeout_message="Timeout ({0}s) waiting for a non-inoperative prompt.") SCMD.ssh.FileExists('/var/run/mcpd.pid', ifc=ssh).\ run_wait(lambda x: x, interval=interval, progress_cb=lambda x: 'mcpd not up...', timeout=timeout) SCMD.ssh.FileExists('/var/run/mprov.pid', ifc=ssh).\ run_wait(lambda x: x is False, progress_cb=lambda x: 'mprov still running...', timeout=timeout, interval=interval) SCMD.ssh.FileExists('/var/run/grub.conf.lock', ifc=ssh).\ run_wait(lambda x: x is False, progress_cb=lambda x: 'grub.lock still running...', timeout=timeout, interval=interval) version = SCMD.ssh.get_version(ifc=ssh) finally: ssh.close() return version
def _wait_after_reboot(self, essential, iso_version): """ Wait until after the reboot is complete. """ if essential: ssh = SSHInterface(address=self.address, port=self.options.ssh_port, username=ROOT_USERNAME, password=ROOT_PASSWORD) else: ssh = SSHInterface(address=self.address, username=self.options.root_username, password=self.options.root_password, port=self.options.ssh_port) timeout = self.options.timeout if essential and iso_version.product.is_bigip and iso_version >= 'bigip 14.0.0': LOG.info('Disabling the 14.0+ password policy...') #SCMD.ssh.DisablePasswordPolicy(address=self.address, # port=self.options.ssl_port).run_wait(timeout=timeout) # XXX: This is via REST not SSH! RCMD.system.DisablePasswordPolicy( address=self.address, port=self.options.ssl_port).run_wait(timeout=timeout) try: SCMD.ssh.GetPrompt(ifc=ssh).\ run_wait(lambda x: x not in ('INOPERATIVE', '!'), timeout=timeout, timeout_message="Timeout ({0}s) waiting for a non-inoperative prompt.", interval=15) SCMD.ssh.FileExists('/var/run/mcpd.pid', ifc=ssh).\ run_wait(lambda x: x, progress_cb=lambda x: 'mcpd not up...', timeout=timeout, interval=15) SCMD.ssh.FileExists('/var/run/mprov.pid', ifc=ssh).\ run_wait(lambda x: x is False, progress_cb=lambda x: 'mprov still running...', timeout=timeout, interval=15) SCMD.ssh.FileExists('/var/run/grub.conf.lock', ifc=ssh).\ run_wait(lambda x: x is False, progress_cb=lambda x: 'grub.lock still running...', timeout=timeout, interval=15) version = SCMD.ssh.get_version(ifc=ssh) finally: ssh.close() return version
class Extractor(Macro): def __init__(self, options, address=None, params=None): self.options = Options(options) self.params = ' '.join(params or []) self.sshparams = Options(device=self.options.device, address=address, timeout=self.options.timeout, username=self.options.username, password=self.options.password) super(Extractor, self).__init__() def prep(self): if not self.options.conf: self.sshifc = SSHInterface(**self.sshparams) self.api = self.sshifc.open() else: self.sshifc = None def cleanup(self): if self.sshifc: self.sshifc.close() def dump_config(self, filename): LOG.info('Dumping configuration...') if self.options.ucs: LOG.info('Extracting ucs %s...', self.options.ucs) self.api.run('mkdir -p /var/local/ucs/tmp') self.api.run('cd /var/local/ucs/tmp && tar xf ../{}'.format(self.options.ucs)) self.api.run('cp /var/local/ucs/tmp/config/bigip.conf {}'.format(filename)) self.api.run('rm -rf /var/local/ucs/tmp') text = self.api.run('cat {}'.format(filename)).stdout elif self.options.conf: with open(self.options.conf) as f: text = f.read() else: if self.sshifc.version > 'bigip 12.1.0': text = self.api.run('tmsh save sys config file {0} no-passphrase && cat {0}'.format(filename)).stdout else: text = self.api.run('tmsh save sys config file {0} && cat {0}'.format(filename)).stdout return text def setup(self): output = SCF_OUTPUT if self.options.cache: if self.api.exists(output): text = self.api.run('cat {0}'.format(output)).stdout else: text = self.dump_config(output) else: text = self.dump_config(output) if self.sshifc and not self.options.cache: self.api.run('rm -f {0}*'.format(output)).stdout LOG.info('Parsing...') config = tmsh.parser(text) LOG.debug(config) all_keys = list(config.keys()) LOG.info('Last key: %s', all_keys[-1]) all_ids = {} for x in all_keys: k = shlex.split(x)[-1] if k in all_ids: all_ids[k].append(config.glob(x)) else: all_ids[k] = [config.glob(x)] vip = config.match("^{}$".format(self.params)) if not vip: raise Exception('No objects found matching "%s"' % self.params) def rec2(root, deps=OrderedDict()): deps.update(root) def guess_key(k, d): this_id = re.split('[:]', k, 1)[0] if not this_id.startswith('/'): this_id = '/Common/%s' % this_id if k.startswith('/'): folder = k.rsplit('/', 1)[0] if folder in all_ids: for x in all_ids[folder]: d.update(x) if this_id in all_ids: for x in all_ids[this_id]: d.update(x) # ipv6.port if re.search(':.*\.[^\.]*$', k): this_id = re.split('[\.]', k, 1)[0] this_id = '/Common/%s' % this_id if this_id in all_ids: for x in all_ids[this_id]: d.update(x) def rec(root, deps=None): if deps is None: deps = {} if isinstance(root, dict): for k, v in root.items(): guess_key(k, deps) rec(v, deps) elif isinstance(root, (set, list, tuple)): for v in root: rec(v, deps) else: root = str(root) assert isinstance(root, str), root guess_key(root, deps) return deps d = rec(root) if d: # Try to avoid circular dependencies list(map(lambda x: d.pop(x), [x for x in d if x in deps])) deps.update(d) rec2(d, deps) return deps ret = rec2(vip) # Sort keys if self.options.sort: ret = tmsh.GlobDict(sorted(iter(ret.items()), key=lambda x: x[0])) return tmsh.dumps(ret)
class ScaleCheck(Macro): def __init__(self, options, address=None, address_iq=None, *args, **kwargs): self.context = O() self.options = O(options) self.options.setifnone('timeout', DEFAULT_TIMEOUT) self.options.setifnone('skip_ping', False) if self.options.device: self.device = ConfigInterface().get_device(options.device) self.address = self.device.address else: self.device = None self.address = address self.options.setifnone('username', DEFAULT_ROOT_USERNAME) self.options.setifnone('password', DEFAULT_ROOT_PASSWORD) self.options.setifnone('admin_username', DEFAULT_ADMIN_USERNAME) self.options.setifnone('admin_password', DEFAULT_ADMIN_PASSWORD) if self.options.device_biq: self.device_biq = ConfigInterface().get_device(options.device_biq) self.address_biq = self.device_biq.address else: self.device_biq = None self.address_biq = address_iq self.options.setifnone('username_iq', DEFAULT_ROOT_USERNAME) self.options.setifnone('password_iq', DEFAULT_ROOT_PASSWORD) self.sshifc = None self.sshifc_biq = None super(ScaleCheck, self).__init__(*args, **kwargs) def prep(self): self.sshifc = SSHInterface(device=self.device, address=self.address, username=self.options.username, password=self.options.password, timeout=self.options.timeout, port=self.options.ssh_port) self.sshifc.open() self.sshifc_biq = SSHInterface(device=self.options.device_biq, address=self.address_biq, username=self.options.username_iq, password=self.options.password_iq, timeout=self.options.timeout, port=self.options.ssh_port) self.sshifc_biq.open() def wait_prompt(self): return SCMD.ssh.GetPrompt(ifc=self.sshifc)\ .run_wait(lambda x: x not in ('INOPERATIVE',), progress_cb=lambda x: 'Still %s...' % x, timeout=self.options.timeout, interval=10) def make_context(self): ctx = self.context ctx.version = SCMD.ssh.get_version(ifc=self.sshifc) ctx.status = self.wait_prompt() LOG.info('Version: {0.product.to_tmos} {0.version} {0.build}'.format( ctx.version)) return ctx def call(self, command): ret = SCMD.ssh.generic(command=command, ifc=self.sshifc) if ret and ret.status: LOG.warn(ret) else: LOG.debug(ret) return ret def clean_storage(self, ctx): has_restjavad = None with EmapiInterface(device=self.device, username=self.options.admin_username, password=self.options.admin_password, port=self.options.ssl_port, address=self.address) as rstifc: try: if ctx.version < "bigip 11.5.0": has_restjavad = rstifc.api.get(DeviceResolver.URI) except EmapiResourceError: LOG.warning("This pre 11.5.0 device hasn't had latest" " REST Framework upgrades") pass self.call("rm -rf /var/log/rest*") self.call("find /var/log -name '*.gz' -exec rm {} \;") # # Remove all files that are: blabla.1.blabla or blabla.1 # self.call("find /var/log -regex '.*[.][1-9].*' -exec rm '{}' \\;") self.call(WIPE_STORAGE) # Because of a bug where restjavad not knowing about icrd, BZ504333. if self.sshifc.version.product.is_bigip: self.call("bigstart restart icrd") with EmapiInterface(device=self.device, username=self.options.admin_username, password=self.options.admin_password, port=self.options.ssl_port, address=self.address) as rstifc: if has_restjavad: wait_args( rstifc.api.get, func_args=[DeviceResolver.URI], progress_message="Waiting for restjavad...", timeout=300, timeout_message="restjavad never came back up after {0}s") def relicense(self, ctx): if ctx.status != 'NO LICENSE': license_date = self.call('grep "License end" %s | cut -d: -f2' % LICENSE_FILE).stdout.strip() license_key = self.call( 'grep "Registration Key" %s | cut -d: -f2' % LICENSE_FILE).stdout.strip() date_format = "%Y%m%d" expire_date = datetime.datetime.strptime(license_date, date_format) delta = expire_date - datetime.datetime.now() if delta > datetime.timedelta(days=15): LOG.debug("%s is NOT within 15 days of being expired. " "Expiration date: %s" % (self.device, license_date)) else: LOG.info("Re-licensing %s. Expiration date: %s" % (self.device, license_date)) ret = self.call('SOAPLicenseClient --verbose --basekey %s' % license_key) LOG.debug("SOAPLicenseClient returned: %s", ret) else: raise MacroError("%s does not have a license. Expect BIG-IP to be " "functional" % self.device) def ping_check(self): bip_selfips = SCMD.tmsh.list("net self", ifc=self.sshifc) self_ips = [x['address'].split('/')[0] for x in bip_selfips.values()] for self_ip in self_ips: self_ip = IPAddress(self_ip) if self_ip.version == 4: COMMAND = "ping -c 1 %s" % self_ip.format(ipv6_full) elif self_ip.version == 6: COMMAND = "ping6 -c 1 %s" % self_ip.format(ipv6_full) # TODO: Find out why we can't ping using ipv6 address on Lowell's BIG-IQs continue else: LOG.info( "You got some weird IP address that isn't ipv4 or ipv6") LOG.info("Ping %s from %s" % (self_ip, self.options.device_biq if self.options.device_biq else self.address_biq)) resp = SCMD.ssh.generic(COMMAND, ifc=self.sshifc_biq) if '100% packet loss' in resp.stdout: LOG.info("device: %s not reachable" % self.device) LOG.debug("device: %s - %s" % (self.device, resp)) raise Exception("device: %s not reachable" % self.device) if self.device: self_ip = IPAddress(self.device.get_discover_address()) LOG.info("Verify given %s from yaml matches one on BIG-IP" % self_ip) for a in bip_selfips.values(): bip_ip = IPAddress(a['address'].split('/')[0]) if a['vlan'] == 'internal' and \ self_ip.version == bip_ip.version: internal_ip = bip_ip break if self_ip.format(ipv6_full) != internal_ip.format(ipv6_full): LOG.info("Internal mismatch: %s. %s != %s." % (self.device, self_ip, internal_ip)) else: LOG.info( "This isn't ran as stages so skipping internal selfip check") def setup(self): ctx = self.make_context() LOG.info("Deleting rest logs, *.gz files, and wiping storage") if ctx.version.product.is_bigip: self.clean_storage(ctx) # Check license and re-license if almost expired self.relicense(ctx) # Check if BIG-IQ can reach BIG-IP if not self.options.skip_ping: self.ping_check() # bigstart restart if BIG-IP is something else other than 'Active' LOG.info("State: %s" % ctx.status) if ctx.status != 'Active': LOG.info("bigstart restart on {0}..".format( self.device if self.device else self.address_biq)) self.call("bigstart restart") self.wait_prompt() def cleanup(self): if self.sshifc is not None: self.sshifc.close() if self.sshifc_biq is not None: self.sshifc_biq.close()
class ConfigPlacer(Macro): SystemConfig = SystemConfig NetworkConfig = NetworkConfig LTMConfig = LTMConfig def __init__(self, options, address=None, *args, **kwargs): self.context = O() self.options = O(options) self.options.setifnone('node_count', DEFAULT_NODES) self.options.setifnone('pool_count', DEFAULT_POOLS) self.options.setifnone('pool_members', DEFAULT_MEMBERS) self.options.setifnone('vip_count', DEFAULT_VIPS) self.options.setifnone('node_start', DEFAULT_NODE_START) self.options.setifnone('partitions', DEFAULT_PARTITIONS) self.options.setifnone('timeout', DEFAULT_TIMEOUT) if self.options.device: self.device = ConfigInterface().get_device(options.device) self.address = self.device.address else: self.device = None self.address = address self.options.setifnone('username', DEFAULT_ROOT_USERNAME) self.options.setifnone('password', DEFAULT_ROOT_PASSWORD) # can.* shortcuts to check for certain features based on the version self.can = O() def can_tmsh(v): return (v.product.is_bigip and v >= 'bigip 11.0.0' or v.product.is_em and v >= 'em 2.0.0' or v.product.is_bigiq) self.can.tmsh = can_tmsh def can_provision(v): return (v.product.is_bigip and v >= 'bigip 10.0.1' or v.product.is_em and v >= 'em 2.0.0' or v.product.is_bigiq) self.can.provision = can_provision def can_lvm(sshifc): return not sshifc('/usr/lib/install/lvmtest').status self.can.lvm = can_lvm self.has = O() def has_asm(s): return bool(SCMD.tmsh.get_provision(ifc=s).asm) self.has.asm = has_asm def has_lvm(s): return not s('/usr/lib/install/lvmtest').status self.has.lvm = has_lvm super(ConfigPlacer, self).__init__(*args, **kwargs) def csv_provider(self, mgmtip): """ Get the static data for a device with a given mgmtip from a CSV file. """ venv_path = os.environ['VIRTUAL_ENV'] csv_location = venv_path + self.options.csv data = O() with open(csv_location, 'rb') as csvfile: reader = csv.DictReader(csvfile) device_row = None for row in reader: # Clean up whitespaces row = {x.strip(): y.strip() for x, y in row.iteritems()} if IPAddress(mgmtip) == IPNetwork(row['mgmtip']).ip: device_row = row break if device_row: if 'hostname' in device_row and device_row['hostname']: data.hostname = device_row['hostname'] data.licenses = {} if 'reg_key' in device_row and device_row['reg_key']: data.licenses.reg_key = [device_row['reg_key']] data.selfip = {} if 'internal' in device_row and device_row['internal']: data.selfip.internal = IPNetwork(device_row['internal']) if 'external' in device_row and device_row['external']: data.selfip.external = IPNetwork(device_row['external']) if 'gateway' in device_row and device_row['gateway']: data.gateway = IPAddress(device_row['gateway']) data.mgmtip = IPNetwork(device_row['mgmtip']) else: LOG.warning("No devices with mgmtip=%s found in CSV." % mgmtip) LOG.debug(data) return data def irack_provider(self, address, username, apikey, mgmtip, timeout=120): """Get the static data for a device with a given mgmtip from iRack.""" data = O() with IrackInterface(address=address, timeout=timeout, username=username, password=apikey, proto='http') as irack: params = dict(address_set__address__in=mgmtip, address_set__type=4) # Locate the static bag for the F5Asset with mgmtip ret = irack.api.staticbag.filter(asset__type=1, **params) if ret.data.meta.total_count == 0: LOG.warning("No devices with mgmtip=%s found in iRack." % mgmtip) return data if ret.data.meta.total_count > 1: raise ValueError("More than one device with mgmtip=%s found in iRack." % mgmtip) bag = ret.data.objects[0] bagid = bag['id'] # Get the hostname ret = irack.api.staticsystem.filter(bag=bagid) assert ret.data.meta.total_count == 1, "No StaticSystem entries for bagid=%s" % bagid data.hostname = ret.data.objects[0].hostname # Get all reg_keys ret = irack.api.staticlicense.filter(bag=bagid) assert ret.data.meta.total_count >= 1, "No StaticLicense entries for bagid=%s" % bagid data.licenses = {} data.licenses.reg_key = [x.reg_key for x in ret.data.objects] # Get all VLAN -> self IPs pairs ret = irack.api.staticaddress.filter(bag=bagid, type=1) data.selfip = {} for o in ret.data.objects: vlan = o.vlan.split('/')[-1] # TODO: IPv6 support...someday? if IPAddress(o.address).version == 4 \ and not int(o.floating): data.selfip[vlan] = IPNetwork("{0.address}/{0.netmask}".format(o)) # Get all mgmt ips ret = irack.api.staticaddress.filter(bag=bagid, type=0) assert ret.data.meta.total_count >= 1, "No StaticAddress entries for bagid=%s" % bagid data.mgmtip = {} for o in ret.data.objects: if IPAddress(o.address).version == 4: data.mgmtip = IPNetwork("{0.address}/{0.netmask}".format(o)) # GW ret = irack.api.staticaddress.filter(bag=bagid, type=3) assert ret.data.meta.total_count >= 1, "No StaticAddress entries for bagid=%s" % bagid for o in ret.data.objects: if IPAddress(o.address).version == 4: data.gateway = IPAddress(o.address) LOG.debug(data) return data def make_context(self): ctx = self.context version_data = SCMD.ssh.get_version(ifc=self.sshifc) ctx.version = version_data ctx.project = SCMD.ssh.parse_keyvalue_file('/VERSION', ifc=self.sshifc).get('project') ctx.platform = SCMD.ssh.get_platform(ifc=self.sshifc)['platform'] ctx.is_cluster = SCMD.ssh.is_cluster(ifc=self.sshifc) ctx.is_vcmp = ctx.platform == 'Z101' ctx.status = SCMD.ssh.GetPrompt(ifc=self.sshifc)\ .run_wait(lambda x: x not in ('INOPERATIVE',), progress_cb=lambda x: 'Still %s...' % x, timeout_message="Timeout ({0}s) waiting for an non-inoperative prompt.", timeout=self.options.timeout) if self.can.provision(ctx.version): ctx.provision = {} modules = SCMD.tmsh.get_provision(ifc=self.sshifc) for k, v in modules.iteritems(): if v: ctx.provision[k] = v['level'] try: tokens = SCMD.ssh.parse_license(ifc=self.sshifc, tokens_only=True) ctx.modules = dict([(k[4:], v) for (k, v) in tokens.items() if k.startswith('mod_')]) ctx.features = dict([(k, v) for (k, v) in tokens.items() if not k.startswith('mod_')]) except SCMD.ssh.LicenseParsingError as e: ctx.modules = {} ctx.features = {} LOG.debug(e) LOG.warning('License file parsing failed. Harmless if this is a clean box.') LOG.info('Version: {0.product.to_tmos} {0.version} {0.build}'.format(ctx.version)) LOG.info('Platform: %s', ctx.platform) LOG.info('Licensed modules: %s', ', '.join(sorted(ctx.modules))) if self.can.provision(ctx.version): LOG.info('Current provisioning: %s', ', '.join(sorted(ctx.provision))) return ctx def prep(self): self.sshifc = SSHInterface(device=self.device, address=self.address, username=self.options.username, password=self.options.password, timeout=self.options.timeout, port=self.options.ssh_port) self.sshifc.open() def set_networking(self, o): ctx = self.context if self.options.mgmtip: o.mgmtip = IPNetwork(self.options.mgmtip) if o.mgmtip.prefixlen == 32: o.mgmtip.prefixlen = 24 LOG.warning('Assuming /24 to the management IP.') elif ctx.is_cluster: ret = SCMD.tmsh.list('sys cluster default', ifc=self.sshifc) o.mgmtip = IPNetwork(ret['sys cluster default']['address']) ret = SCMD.tmsh.list('sys management-route', ifc=self.sshifc) # Sometimes it's defined as "default" some other times it's "0.0.0.0" if ret: o.gateway = IPAddress(ret.values()[0]['gateway']) else: # Try to find the existing IP configuration on management interface. ret = self.sshifc("ethconfig --getcurrent") bits = ret.stdout.split() o.mgmtip = IPNetwork("{0[0]}/{0[1]}".format(bits)) o.gateway = IPAddress(bits[2]) # If None or IPAddress('0.0.0.0') if not o.gateway: o.gateway = IPAddress(self.options.mgmtgw) if self.options.mgmtgw \ else o.mgmtip.broadcast - 1 # Preserve the DHCP flag for the management interface if self.can.tmsh(ctx.version): ret = SCMD.tmsh.list('sys db dhclient.mgmt', ifc=self.sshifc) o.dhcp = ret['sys db dhclient.mgmt']['value'] == 'enable' def set_provisioning(self, o): ctx = self.context if not self.can.provision(ctx.version): LOG.info('Provision not supported on the target') return o.provision = {} provision = [] if self.options.get('provision'): for prov in self.options['provision'].split(','): if prov: bits = prov.lower().split(':', 1) if len(bits) > 1: module, level = bits assert level in ('minimum', 'nominal', 'dedicated') else: module = bits[0].strip() level = 'nominal' if module == 'ltm' else 'minimum' provision.append((module, level)) if provision: v = ctx.version for module, level in provision: if module == 'afm' and not (v.product.is_bigip and v >= 'bigip 11.3.0'): LOG.warning('AFM cannot be provisioned on this target.') continue o.provision[module] = level else: # Preserve provisioning. modules = SCMD.tmsh.get_provision(ifc=self.sshifc) for module, level_kv in modules.iteritems(): if level_kv: level = level_kv['level'] o.provision[module] = level def set_users(self, o): ctx = self.context v = ctx.version o.users = {} o.users[OUR_ADMIN_USERNAME] = 'admin' if v.product.is_bigip or v.product.is_em: o.users.g = 'guest' o.users.o = 'operator' o.users.ae = 'application-editor' o.users.m = 'manager' o.users.um = 'user-manager' o.users.ra = 'resource-admin' if v.product.is_bigip and v >= 'bigip 11.3.0': o.users.cm = 'certificate-manager' o.users.im = 'irule-manager' o.users.au = 'auditor' o.users.asa = 'web-application-security-administrator' o.users.ase = 'web-application-security-editor' o.users.fm = 'firewall-manager' if v.product.is_bigiq: o.users.g = 'guest' o.users.fw = 'firewall-manager' def set_vlans(self, o): ctx = self.context lacp = self.options.trunks_lacp if ctx.platform.startswith('A'): o.trunks = {} eth1 = 'internal' # Trunk names eth2 = 'external' elif ctx.platform.startswith('Z'): # VEs can may have a variable number of TMM interfaces. # Adjusting accordingly. ret = SCMD.tmsh.list('net interface', ifc=self.sshifc) interfaces = sorted([x.split()[2] for x in ret.keys() if x.split()[2] != 'mgmt']) eth1, eth2 = (interfaces + [None, None])[:2] else: eth1 = '1.1' eth2 = '1.2' o.vlans = {} if ctx.is_vcmp: # No trunks. They will be automatically generated on VCMP guests. eth1 = eth2 = None ret = SCMD.tmsh.list('net vlan', ifc=self.sshifc) for key, obj in ret.items(): s = Mirror(key, obj) o.vlans[s.name] = s if ctx.platform == 'A100': o.trunks.internal = O(interfaces=['1/2.1', '2/2.1', '3/2.1', '4/2.1'], lacp=lacp) o.trunks.external = O(interfaces=['1/2.2', '2/2.2', '3/2.2', '4/2.2'], lacp=lacp) elif ctx.platform in ('A107', 'A109', 'A111'): o.trunks.internal = O(interfaces=['1/1.1', '2/1.1', '3/1.1', '4/1.1'], lacp=lacp) o.trunks.external = O(interfaces=['1/1.2', '2/1.2', '3/1.2', '4/1.2'], lacp=lacp) elif ctx.platform == 'A108': o.trunks.internal = O(interfaces=['1/1.1', '2/1.1', '3/1.1', '4/1.1', '5/1.1', '6/1.1', '7/1.1', '8/1.1'], lacp=lacp) o.trunks.external = O(interfaces=['1/1.2', '2/1.2', '3/1.2', '4/1.2', '5/1.2', '6/1.2', '7/1.2', '8/1.2'], lacp=lacp) def parse_vlan_options(kind): specs = O(tagged=[], untagged=[]) if kind == 'internal': tags = self.options.vlan_internal if not tags: specs.untagged.append(eth1) if kind == 'external': tags = self.options.vlan_external if not tags: specs.untagged.append(eth2) if tags: for tag in re.split('\s+', tags): key, value = tag.split('=') if key == 'tag': specs.tag = value else: specs[key].append(value) return specs if eth1: o.vlans.internal = O(**parse_vlan_options('internal')) if eth2: o.vlans.external = O(**parse_vlan_options('external')) # def fix_cmi(self): # """ # The main goal of this method is to empty all device groups, otherwise # the config load will fail. There should be a better way of doing this. # """ # ctx = self.context # if self.can.tmsh(ctx.version): # LOG.debug('Emptying CM device groups...') # ret = SCMD.tmsh.list('cm device-group', ifc=self.sshifc) # for key, value in ret.iteritems(): # devices = value.get('devices') # if devices: # ret = self.sshifc('tmsh modify %s devices delete { %s }' % # (key, ' '.join(devices.keys()))) # if ret.status: # LOG.warning(ret) def dump(self, tree, ctx, func=None): f = sys.stdout # @UndefinedVariable LOG.info('Rendering configuration file...') f.write(HEADER) tree.render(stream=f, func=func) def load(self, tree, ctx, func=None): with self.sshifc.api.sftp().open(SCF_FILENAME, 'w') as f: LOG.info('Rendering configuration file...') f.write(HEADER) tree.render(stream=f, func=func) if self.can.tmsh(ctx.version): if self.options.verify: LOG.info('Verifying configuration...') ret = self.sshifc('tmsh load sys config file %s verify' % SCF_FILENAME) LOG.info(ret.stdout) else: LOG.info('Loading configuration...') SCMD.ssh.generic('tmsh load sys config file %s' % SCF_FILENAME, ifc=self.sshifc) else: LOG.info('Loading configuration...') SCMD.ssh.generic('b import %s' % SCF_FILENAME, ifc=self.sshifc) def save(self, ctx): LOG.info('Saving configuration...') if self.can.tmsh(ctx.version): SCMD.ssh.generic('tmsh save sys config partitions all', ifc=self.sshifc) else: SCMD.ssh.generic('b save all', ifc=self.sshifc) def reset_trust(self): v = self.context.version if v.product.is_bigiq and v > 'bigiq 4.0': return if self.can.tmsh(v): LOG.info('Resetting trust...') SCMD.ssh.Generic('tmsh delete cm trust-domain all', ifc=self.sshifc).\ run_wait(lambda x: x.status == 0, progress_cb=lambda x: 'delete trust-domain retry...', timeout=self.options.timeout) def ready_wait(self): ctx = self.context if self.options.get('dry_run'): return LOG.info('Waiting for reconfiguration...') timeout = self.options.timeout SCMD.ssh.FileExists('/var/run/mprov.pid', ifc=self.sshifc)\ .run_wait(lambda x: x is False, progress_cb=lambda x: 'mprov still running...', timeout=timeout) if (self.can.provision(ctx.version) and self.has.asm(self.sshifc) and self.has.lvm(self.sshifc)): SCMD.ssh.FileExists('/var/lib/mysql/.moved.to.asmdbvol', ifc=self.sshifc)\ .run_wait(lambda x: x, progress_cb=lambda x: 'ASWADB still not there...', timeout=timeout) # Wait for ASM config server to come up. if self.can.provision(ctx.version) and self.has.asm(self.sshifc): LOG.info('Waiting for ASM config server to come up...') SCMD.ssh.Generic(r'netstat -anp|grep -P ":9781\s+(0\.0\.0\.0|::):\*\s+LISTEN"|wc -l', ifc=self.sshifc).run_wait(lambda x: int(x.stdout), progress_cb=lambda x: 'ASM cfg server not up...', timeout=timeout) LOG.info('Waiting for Active prompt...') s = SCMD.ssh.GetPrompt(ifc=self.sshifc)\ .run_wait(lambda x: x in ('Active', 'Standby', 'ForcedOffline', 'RESTART DAEMONS', 'REBOOT REQUIRED'), progress_cb=lambda x: 'Still not active (%s)...' % x, timeout_message="Timeout ({0}s) waiting for an 'Active' prompt.", timeout=timeout) if s == 'RESTART DAEMONS': LOG.info('Restarting daemons...') self.call('bigstart restart') SCMD.ssh.GetPrompt(ifc=self.sshifc)\ .run_wait(lambda x: x in ('Active', 'Standby', 'ForcedOffline'), progress_cb=lambda x: 'Still not active (%s)...' % x, timeout=timeout) elif s == 'REBOOT REQUIRED': LOG.warn('A manual reboot is required.') if SCMD.ssh.file_exists('/var/run/grub.conf.lock', ifc=self.sshifc): self.ssh.api.remove('/var/run/grub.conf.lock') def ssh_key_exchange(self): if self.options.get('no_sshkey'): return o = O() o.device = self.device o.username = self.options.username o.password = self.options.password o.port = self.options.ssh_port cs = KeySwap(o, address=self.address) cs.run() def ssl_signedcert_install(self, hostname): # BUG: Sometimes user 'a' role gets downgraded to 'guest' at this point. # It's still unclear why this happens. if self.context.version >= 'bigip 11.6' or self.context.version >= 'bigiq 4.6': SCMD.ssh.generic('tmsh modify auth user %s partition-access modify {all-partitions {role admin}}' % OUR_ADMIN_USERNAME, ifc=self.sshifc) else: SCMD.ssh.generic('tmsh modify auth user %s role admin' % OUR_ADMIN_USERNAME, ifc=self.sshifc) o = O() o.admin_username = OUR_ADMIN_USERNAME o.root_username = self.options.username o.ssh_port = self.options.ssh_port o.ssl_port = self.options.ssl_port o.verbose = self.options.verbose o.timeout = self.options.timeout LOG.info('Pushing the key/certificate pair') # The special a:a admin user is used here. It should be included in the # remote_config yaml config in the users.administrator section. o.admin_password = OUR_ADMIN_PASSWORD o.root_password = self.options.password if hostname: o.alias = [hostname] cs = WebCert(o, address=self.address) cs.run() def bigiq_special_selfip_handling(self, tree, ctx): "Because the use of tmsh has become frown upon starting with 4.2" v = ctx.version if v.product.is_bigiq and v >= 'bigiq 4.2.0': # XXX: Because sometimes user 'a' set via tmsh is not picked up. self.call('bigstart restart restjavad') with EmapiInterface(username=OUR_ADMIN_USERNAME, password=OUR_ADMIN_PASSWORD, port=self.options.ssl_port, address=self.address) as rstifc: payload = O(selfIpAddresses=[]) for self_ip in enumerate_stamps(tree, SelfIP, include_common=False): # XXX: BIGIQ 4.2 API only supports untagged. for iface in self_ip.vlan.untagged: payload.selfIpAddresses.append(O(address=str(self_ip.address), vlan=self_ip.vlan.name, iface=iface)) LOG.debug('EasySetup payload: %s', payload) wait_args(rstifc.api.patch, func_args=(EasySetup.URI, payload), timeout=180, interval=5, timeout_message="Can't patch selfIPs ready after {0}s") # def bigiq_special_selfip_handling(self, tree, ctx): # "Because the use of tmsh has become frown upon starting with 4.2" # v = ctx.version # if v.product.is_bigiq and v >= 'bigiq 4.2.0': # # XXX: Because sometimes user 'a' set via tmsh is not picked up. # self.call('bigstart restart restjavad') # with EmapiInterface(username=OUR_ADMIN_USERNAME, # password=OUR_ADMIN_PASSWORD, # port=self.options.ssl_port, # address=self.address) as rstifc: # LOG.info('Waiting for restjavad to come up...') # vlans = wait_args(rstifc.api.get, func_args=(NetworkVlan.URI,), # timeout=60, interval=5, # timeout_message="Can't get VLANs in {0}s") # vlans_by_name = dict(((x.name, x) for x in vlans['items'])) # for self_ip in enumerate_stamps(tree, SelfIP, include_common=False): # # XXX: BIGIQ 4.2 API only supports untagged. # # And only one interface per VLAN. # payload = NetworkSelfip() # payload.name = self_ip.name # payload.address = str(self_ip.address) # payload.vlanReference = vlans_by_name[self_ip.vlan.name] # rstifc.api.post(NetworkSelfip.URI, payload=payload) def calculate_vip_start(self, vip_start, mgmtip, selfip_external): if not selfip_external and not vip_start: LOG.info('Skipping auto VIP generation.') return if selfip_external: if not isinstance(selfip_external, IPNetwork): selfip_external = IPNetwork(selfip_external) if selfip_external.prefixlen == 32: LOG.info('Self IP external has no prefix. Assuming /16.') selfip_external.prefixlen = 16 if vip_start is None: # '1.1.1.1/24' -> '1.1.1.0/24' cidr = "{0.network}/{0.prefixlen}".format(mgmtip) host_id = mgmtip.ip.value - mgmtip.network.value subnet_index = SUBNET_MAP.get(cidr) if subnet_index is None: LOG.warning('The %s subnet was not found! ' 'Skipping Virtual Servers.' % cidr) return # Start from 10.11.50.0 - 10.11.147.240 (planned for 10 subnets, 8 VIPs each) offset = 1 + 50 * 256 + DEFAULT_VIPS * (256 * subnet_index + host_id) vip_start = selfip_external.network + offset else: vip_start = IPAddress(vip_start) return vip_start def call(self, *args, **kwargs): ret = SCMD.ssh.generic(command=args[0], ifc=self.sshifc) if ret and ret.status: LOG.warn(ret) else: LOG.debug(ret) return ret def license(self, ctx, regkey): if ctx.status == 'NO LICENSE' and not regkey: raise Exception('The box needs to be relicensed first.' 'Provide --license <regkey>') # Status could be '' in some situations when the license is invalid. if regkey and (ctx.status in ['LICENSE INOPERATIVE', 'NO LICENSE', ''] or self.options.get('force_license')): LOG.info('Licensing...') self.call('SOAPLicenseClient --verbose --basekey %s' % regkey) # We need to re-set the modules based on the new license tokens = SCMD.ssh.parse_license(ifc=self.sshifc, tokens_only=True) ctx.modules = dict([(k[4:], v) for (k, v) in tokens.items() if k.startswith('mod_')]) ctx.features = dict([(k, v) for (k, v) in tokens.items() if not k.startswith('mod_')]) # Tomcat doesn't like the initial licensing through CLI if ctx.version.product.is_em: ret = self.call('bigstart restart tomcat') elif ctx.status in ['LICENSE EXPIRED', 'REACTIVATE LICENSE']: LOG.info('Re-Licensing...') ret = self.call('SOAPLicenseClient --verbose --basekey `grep ' '"Registration Key" /config/bigip.license|' 'cut -d: -f2`') LOG.debug("SOAPLicenseClient returned: %s", ret) else: LOG.info('Skipping licensing.') def load_default_config(self): ctx = self.context if self.can.tmsh(ctx.version): LOG.info('Importing default config...') self.call('tmsh load sys config default') else: LOG.info('Importing default config...') self.call('b import default') if ctx.version.product.is_bigiq: self.call(WIPE_STORAGE) def setup(self): provider = O() if not self.options.no_irack and not self.options.csv: LOG.info("Using data from iRack") provider = self.irack_provider(address=self.options.irack_address, username=self.options.irack_username, apikey=self.options.irack_apikey, mgmtip=self.address, timeout=self.options.timeout) elif self.options.csv: LOG.info("Using data from CSV: %s" % self.options.csv) provider = self.csv_provider(mgmtip=self.address) ctx = self.make_context() # System o = O() o.partitions = self.options.partitions o.nameservers = DNS_SERVERS if self.options.dns_servers is None else \ self.options.dns_servers o.suffixes = DNS_SUFFIXES if self.options.dns_suffixes is None else \ self.options.dns_suffixes o.ntpservers = NTP_SERVERS if self.options.ntp_servers is None else \ self.options.ntp_servers o.smtpserver = 'mail.f5net.com' o.hostname = self.options.hostname or provider.get('hostname') o.timezone = self.options.timezone self.set_networking(o) self.set_provisioning(o) self.set_users(o) if provider.mgmtip and (o.mgmtip.ip != provider.mgmtip.ip or o.mgmtip.cidr != provider.mgmtip.cidr): LOG.warning('Management address mismatch. iRack/CSV has {0} but found {1}. iRack/CSV will take precedence.'.format(provider.mgmtip, o.mgmtip)) o.mgmtip = provider.mgmtip mgmtip = o.mgmtip if provider.gateway and o.gateway != provider.gateway: LOG.warning('Default gateway address mismatch. iRack/CSV has {0} but found {1}. iRack/CSV will take precedence.'.format(provider.gateway, o.gateway)) o.gateway = provider.gateway tree = self.SystemConfig(self.context, **o).run() if self.options.clean: self.load_default_config() if not self.options.stdout: self.load(tree, ctx, func=lambda x: not isinstance(x, Partition)) if not self.options.stdout: # XXX: Add any given DNS before attempting to relicense. # Licensing may need to resolve the license server hostname. if self.can.tmsh(ctx.version): self.call('tmsh modify sys dns name-servers add { %s }' % ' '.join(o.nameservers)) self.license(ctx, self.options.license or provider and provider.licenses.reg_key[0]) if self.options.clean: self.reset_trust() return # Network o = O() o.tree = tree self.set_vlans(o) o.selfips = {} selfip_internal = self.options.selfip_internal or provider and provider.selfip.internal selfip_external = self.options.selfip_external or provider and provider.selfip.external if selfip_internal: o.selfips.internal = [O(address=selfip_internal)] # o.selfips.internal.append(O(address=ip4to6(selfip_internal), name='int_6')) o.selfips.internal.append(O(address=ip4to6(selfip_internal))) if selfip_external: o.selfips.external = [O(address=selfip_external)] # o.selfips.external.append(O(address=ip4to6(selfip_external), name='ext_6')) o.selfips.external.append(O(address=ip4to6(selfip_external))) tree = self.NetworkConfig(self.context, **o).run() # LTM o = O() o.tree = tree o.nodes = self.options.node_count o.pools = self.options.pool_count o.members = self.options.pool_members o.vips = self.options.vip_count o.node1 = self.options.node_start o.vip1 = self.calculate_vip_start(self.options.vip_start, mgmtip, selfip_external) o.with_monitors = not self.options.no_mon tree = self.LTMConfig(self.context, **o).run() if self.options.stdout: self.dump(tree, ctx) return self.load(tree, ctx) self.reset_trust() self.ready_wait() self.save(ctx) self.ssh_key_exchange() self.ssl_signedcert_install(o.hostname) self.bigiq_special_selfip_handling(tree, ctx) def cleanup(self): self.sshifc.close()