示例#1
0
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.')
示例#2
0
文件: unmerge.py 项目: xiaotdl/nosest
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.')
示例#3
0
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()
示例#4
0
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)
示例#5
0
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()