Esempio n. 1
0
    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)

        super(ScaleCheck, self).__init__(*args, **kwargs)
Esempio n. 2
0
    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)
Esempio n. 3
0
    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()
Esempio n. 4
0
    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
Esempio n. 5
0
    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()
Esempio n. 6
0
    def __init__(self, guests, *args, **kwargs):
        super(VcmpPlacer, self).__init__(*args, **kwargs)
        # Case when there is no vCMP guests to setup.
        if not guests:
            guests = []

        self.guests = []
        for guest in guests:
            if isinstance(guest, DeviceAccess):
                item = guest
            else:
                if len(guest.split(':')) > 2:
                    address, gw, name = guest.split(':')
                elif len(guest.split(':')) > 1:
                    address, gw = guest.split(':')
                    name = None
                else:
                    address, gw, name = guest, None, None

                item = O()
                item.specs = O()
                spec = item.specs
                spec.address = IPNetwork(address)
                spec.gw = IPAddress(gw) if gw else None
                spec.cores = self.options.get('cores')
                spec.name = name
            self.guests.append(item)

        if isinstance(self.device, DeviceAccess):
            self.options.admin_username = self.device.get_admin_creds(
            ).username or ADMIN_USERNAME
            self.options.admin_password = self.device.get_admin_creds(
            ).password or ADMIN_PASSWORD

        self.options.setifnone('admin_username', ADMIN_USERNAME)
        self.options.setifnone('admin_password', ADMIN_PASSWORD)

        # Assumed we are using vcmp provisioning here if we are not reverting
        # BIG-IP back to whatever provision.
        if not self.options.revert:
            self.options.provision = 'vcmp'
Esempio n. 7
0
 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")
Esempio n. 8
0
    def get_provider(self, address, csv):
        provider = O()
        if not self.options.no_irack and not 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=address,
                timeout=self.options.timeout)
        elif csv:
            LOG.info("Using data from CSV: %s" % csv)
            provider = self.csv_provider(mgmtip=address, csv_rpath=csv)

        return provider
Esempio n. 9
0
        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
Esempio n. 10
0
    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
Esempio n. 11
0
'''
Created on Apr 9, 2013

@author: jono
'''
import logging
from f5test.macros.tmosconf.placer import ConfigPlacer

if __name__ == '__main__':
    from f5test.base import AttrDict as O
    from f5test.utils.version import Version
    from f5test.macros.tmosconf.base import (SystemConfig, NetworkConfig,
                                             LTMConfig, AFMConfig, APMConfig)
    logging.basicConfig(level=logging.INFO)

    context = O(version=Version('bigip 12.1.0'),
                provision=O(afm='nominal', ltm='nominal', apm='nominal'))

    o = O()
    o.partitions = 1
    o.mgmtip = '10.1.2.3/24'
    o.gateway = '10.1.2.254'
    o.nameservers = ['172.27.1.1']
    o.ntpservers = ['ntp']
    o.provision = {}
    o.provision.ltm = 'nominal'
    o.users = {}
    o.users.g = 'guest'
    o.users.a = 'admin'
    o.users.o = 'operator'
    #o.users.ra = O(password='******', role='guest')
    o.smtpserver = 'mail.aaaa.bb'
Esempio n. 12
0
    def setup(self):
        ctx = self.make_context()
        if self.options.revert:
            LOG.info("Reverting by provisioning to %s" %
                     self.options.provision)

        if self.options.clean_only and 'vcmp' in ctx.provision.keys():
            # Disable any running vCMP Guests then delete them
            self.delete_guests()
            self.clean_vdisk()
            return

        if not self.guests and not self.revert:
            LOG.warning("No vCMP Guests to setup. Just use the normal "
                        "configurator if you only need to do vcmp "
                        "provisioning only...")
            return

        provider = self.get_provider(self.address, self.options.csv)
        reboot = True

        # TODO: We will need to add more logic here to check if the current
        # running guests are the same as our config. If so, leave it, otherwise
        # clean it out.
        if 'vcmp' in ctx.provision.keys():
            # Disable any running vCMP Guests then delete them
            self.delete_guests()
            self.clean_vdisk()

        if self.options.provision in ctx.provision.keys():
            LOG.info("No need for reboot...")
            reboot = False
        # System
        o = O()
        o.hostname = self.options.hostname or provider.get('hostname')
        self.set_networking(o)
        self.set_provisioning(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

        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, partitions=0, **o).run()

        # Skip this if we are reverting back to another provision
        if not self.options.revert:
            # vCMP Guests
            LOG.info("Generating vCMP Guests...")

            # Look for image and hotfix with the same version as BIG-IP Host.
            target_version = ctx.version.version
            images = SCMD.ssh.generic('ls /shared/images',
                                      ifc=self.sshifc).stdout
            hotfix = base = None
            for image in images.split():
                if target_version in image and 'Hotfix' in image:
                    hotfix = image
                elif target_version in image:
                    base = image

                if hotfix and image:
                    break

            for i, guest in enumerate(self.guests):
                csv = self.device.specs.csv if isinstance(
                    self.device, DeviceAccess) else self.options.csv

                # Just IP, no netmask info like x.x.x.x/yy
                ip = guest.specs.address.ip if isinstance(
                    guest.specs.address, IPNetwork) else guest.address
                provider = self.get_provider(ip, csv)

                management_ip = provider.mgmtip or IPNetwork(
                    guest.specs.address)
                management_gw = provider.gateway or guest.specs.gw

                if str(management_ip.netmask) == '255.255.255.255':
                    LOG.warning(
                        "Mgmt netmask probably was not set! Defaulting to "
                        "/24 prefix.")
                    management_ip.prefixlen = DEFAULT_NETMASK_PREFIX

                if not management_gw:
                    LOG.warning("Gateway not set!!")
                    default_gw = management_ip.broadcast - 1
                    management_gw = default_gw
                    LOG.warning("Defaulting to %s" % default_gw)

                guest_name = guest.specs.name or GUEST_NAME % (i)
                o = O(tree=tree,
                      name=guest_name,
                      management_ip=management_ip,
                      management_gw=management_gw,
                      initial_image=base,
                      initial_hotfix=hotfix)
                if guest.specs.get('cores'):
                    o.cores_per_slot = guest.specs.get('cores')

                tree = self.vCMPConfig(self.context, **o).run()

        if self.options.stdout:
            self.dump(tree, ctx)
            return

        self.load(tree, ctx)

        # If BIG-IP isn't already provisioned, a reboot will happen.
        timeout = self.options.timeout
        if reboot:
            ic = self.icifc.open()
            uptime = ic.System.SystemInfo.get_uptime()
            LOG.info("Pausing 30 seconds to wait until reboot has happened...")
            time.sleep(30)

            if uptime:
                ICMD.system.HasRebooted(uptime, ifc=self.icifc).\
                    run_wait(timeout=timeout)
                LOG.info('Device is rebooting...')

            LOG.info('Wait for box to be ready...')
            self.wait_reboot(self.icifc)

        self.reset_trust()
        self.ready_wait()

        # Skip this if we are reverting back to another provision
        if not self.options.revert:
            # There shouldn't be any virtual disks so clean out any remaining ones
            # from previous runs
            self.clean_vdisk()

            # Deploy vCMP Guests.
            guests = SCMD.tmsh.list('vcmp guest', ifc=self.sshifc)
            for guest, items in guests.iteritems():
                if 'state' not in items or items['state'] != 'deployed':
                    LOG.info("Deploying %s..." % guest.split()[-1])
                    SCMD.tmsh.run(guest + ' state deployed',
                                  command='modify',
                                  ifc=self.sshifc)

            # Wait until vCMP guests are running
            timeout = VCMP_TIMEOUT * (
                len(self.guests) / 6 + 1
            )  # Only 6 vCMP Guests can start in parallel
            for guest in self.guests:
                try:
                    ip = guest.specs.address.ip if isinstance(
                        guest.specs.address, IPNetwork) else guest.address
                    icifc = IcontrolInterface(address=ip,
                                              username=ADMIN_USERNAME,
                                              password=ADMIN_PASSWORD,
                                              port=self.options.ssl_port)
                    LOG.info('Wait for %s to be ready...' % ip)
                    self.wait_reboot(icifc, timeout=timeout)
                finally:
                    icifc.close()

        self.save(ctx)
        self.ssh_key_exchange()
Esempio n. 13
0
    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)
Esempio n. 14
0
    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'))