http://git-wip-us.apache.org/repos/asf/cloudstack/blob/0e223d67/tools/marvin/marvin/lib/common.py ---------------------------------------------------------------------- diff --cc tools/marvin/marvin/lib/common.py index f08cffb,0000000..62dab25 mode 100644,000000..100644 --- a/tools/marvin/marvin/lib/common.py +++ b/tools/marvin/marvin/lib/common.py @@@ -1,975 -1,0 +1,1097 @@@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +"""Common functions +""" + +# Import Local Modules +from marvin.cloudstackAPI import (listConfigurations, + listPhysicalNetworks, + listRegions, + addNetworkServiceProvider, + updateNetworkServiceProvider, + listDomains, + listZones, + listPods, + listOsTypes, + listTemplates, + updateResourceLimit, + listRouters, + listNetworks, + listClusters, + listSystemVms, + listStoragePools, + listVirtualMachines, + listLoadBalancerRuleInstances, + listFirewallRules, + listVolumes, + listIsos, + listAccounts, + listSnapshotPolicies, + listDiskOfferings, + listVlanIpRanges, + listUsageRecords, + listNetworkServiceProviders, + listHosts, + listPublicIpAddresses, + listPortForwardingRules, + listLoadBalancerRules, + listSnapshots, + listUsers, + listEvents, + listServiceOfferings, + listVirtualRouterElements, + listNetworkOfferings, + listResourceLimits, + listVPCOfferings) - from marvin.lib.base import (Configurations, - NetScaler, - Template, - Resources, - PhysicalNetwork, - Host) - from marvin.lib.utils import (get_process_status, - xsplit) ++ ++ ++ + +from marvin.sshClient import SshClient ++from marvin.codes import (PASS, ISOLATED_NETWORK, VPC_NETWORK, ++ BASIC_ZONE, FAIL, NAT_RULE, STATIC_NAT_RULE) +import random - from utils import * - from base import * ++from marvin.lib.utils import * ++from marvin.lib.base import * +from marvin.codes import PASS - from marvin.lib.utils import validateList ++ + +# Import System modules +import time + + +def is_config_suitable(apiclient, name, value): + """ + Ensure if the deployment has the expected `value` for the global setting `name' + @return: true if value is set, else false + """ + configs = Configurations.list(apiclient, name=name) + assert( + configs is not None and isinstance( + configs, + list) and len( + configs) > 0) + return configs[0].value == value + + +def wait_for_cleanup(apiclient, configs=None): + """Sleeps till the cleanup configs passed""" + + # Configs list consists of the list of global configs + if not isinstance(configs, list): + return + for config in configs: + cmd = listConfigurations.listConfigurationsCmd() + cmd.name = config + cmd.listall = True + try: + config_descs = apiclient.listConfigurations(cmd) + except Exception as e: + raise Exception("Failed to fetch configurations: %s" % e) + + if not isinstance(config_descs, list): + raise Exception("List configs didn't returned a valid data") + + config_desc = config_descs[0] + # Sleep for the config_desc.value time + time.sleep(int(config_desc.value)) + return + + +def add_netscaler(apiclient, zoneid, NSservice): + """ Adds Netscaler device and enables NS provider""" + + cmd = listPhysicalNetworks.listPhysicalNetworksCmd() + cmd.zoneid = zoneid + physical_networks = apiclient.listPhysicalNetworks(cmd) + if isinstance(physical_networks, list): + physical_network = physical_networks[0] + + cmd = listNetworkServiceProviders.listNetworkServiceProvidersCmd() + cmd.name = 'Netscaler' + cmd.physicalnetworkid = physical_network.id + nw_service_providers = apiclient.listNetworkServiceProviders(cmd) + + if isinstance(nw_service_providers, list): + netscaler_provider = nw_service_providers[0] + else: + cmd1 = addNetworkServiceProvider.addNetworkServiceProviderCmd() + cmd1.name = 'Netscaler' + cmd1.physicalnetworkid = physical_network.id + netscaler_provider = apiclient.addNetworkServiceProvider(cmd1) + + netscaler = NetScaler.add( + apiclient, + NSservice, + physicalnetworkid=physical_network.id + ) + if netscaler_provider.state != 'Enabled': + cmd = updateNetworkServiceProvider.updateNetworkServiceProviderCmd() + cmd.id = netscaler_provider.id + cmd.state = 'Enabled' + apiclient.updateNetworkServiceProvider(cmd) + + return netscaler + + +def get_region(apiclient, region_id=None, region_name=None): + ''' + @name : get_region + @Desc : Returns the Region Information for a given region id or region name + @Input : region_name: Name of the Region + region_id : Id of the region + @Output : 1. Region Information for the passed inputs else first Region + 2. FAILED In case the cmd failed + ''' + cmd = listRegions.listRegionsCmd() + if region_name is not None: + cmd.name = region_name + if region_id is not None: + cmd.id = region_id + cmd_out = apiclient.listRegions(cmd) + return FAILED if validateList(cmd_out)[0] != PASS else cmd_out[0] + + +def get_domain(apiclient, domain_id=None, domain_name=None): + ''' + @name : get_domain + @Desc : Returns the Domain Information for a given domain id or domain name + @Input : domain id : Id of the Domain + domain_name : Name of the Domain + @Output : 1. Domain Information for the passed inputs else first Domain + 2. FAILED In case the cmd failed + ''' + cmd = listDomains.listDomainsCmd() + + if domain_name is not None: + cmd.name = domain_name + if domain_id is not None: + cmd.id = domain_id + cmd_out = apiclient.listDomains(cmd) + if validateList(cmd_out)[0] != PASS: + return FAILED + return cmd_out[0] + + +def get_zone(apiclient, zone_name=None, zone_id=None): + ''' + @name : get_zone + @Desc :Returns the Zone Information for a given zone id or Zone Name + @Input : zone_name: Name of the Zone + zone_id : Id of the zone + @Output : 1. Zone Information for the passed inputs else first zone + 2. FAILED In case the cmd failed + ''' + cmd = listZones.listZonesCmd() + if zone_name is not None: + cmd.name = zone_name + if zone_id is not None: + cmd.id = zone_id + + cmd_out = apiclient.listZones(cmd) + + if validateList(cmd_out)[0] != PASS: + return FAILED + ''' + Check if input zone name and zone id is None, + then return first element of List Zones command + ''' + return cmd_out[0] + + +def get_pod(apiclient, zone_id=None, pod_id=None, pod_name=None): + ''' + @name : get_pod + @Desc : Returns the Pod Information for a given zone id or Zone Name + @Input : zone_id: Id of the Zone + pod_name : Name of the Pod + pod_id : Id of the Pod + @Output : 1. Pod Information for the pod + 2. FAILED In case the cmd failed + ''' + cmd = listPods.listPodsCmd() + + if pod_name is not None: + cmd.name = pod_name + if pod_id is not None: + cmd.id = pod_id + if zone_id is not None: + cmd.zoneid = zone_id + + cmd_out = apiclient.listPods(cmd) + + if validateList(cmd_out)[0] != PASS: + return FAILED + return cmd_out[0] - - +def get_template( + apiclient, zone_id=None, ostype_desc=None, template_filter="featured", template_type='BUILTIN', + template_id=None, template_name=None, account=None, domain_id=None, project_id=None, + hypervisor=None): + ''' + @Name : get_template + @Desc : Retrieves the template Information based upon inputs provided + Template is retrieved based upon either of the inputs matched + condition + @Input : returns a template" + @Output : FAILED in case of any failure + template Information matching the inputs + ''' + cmd = listTemplates.listTemplatesCmd() + cmd.templatefilter = template_filter + if domain_id is not None: + cmd.domainid = domain_id + if zone_id is not None: + cmd.zoneid = zone_id + if template_id is not None: + cmd.id = template_id + if template_name is not None: + cmd.name = template_name + if hypervisor is not None: + cmd.hypervisor = hypervisor + if project_id is not None: + cmd.projectid = project_id + if account is not None: + cmd.account = account + + ''' + Get the Templates pertaining to the inputs provided + ''' + list_templatesout = apiclient.listTemplates(cmd) + if validateList(list_templatesout)[0] != PASS: + return FAILED + + for template in list_templatesout: + if template.isready and template.templatetype == template_type: + return template + ''' + Return default first template, if no template matched + ''' + return list_templatesout[0] + + +def download_systemplates_sec_storage(server, services): + """Download System templates on sec storage""" + + try: + # Login to management server + ssh = SshClient( + server["ipaddress"], + server["port"], + server["username"], + server["password"] + ) + except Exception: + raise Exception("SSH access failed for server with IP address: %s" % + server["ipaddess"]) + # Mount Secondary Storage on Management Server + cmds = [ + "mkdir -p %s" % services["mnt_dir"], + "mount -t nfs %s:/%s %s" % ( + services["sec_storage"], + services["path"], + services["mnt_dir"] + ), + "%s -m %s -u %s -h %s -F" % ( + services["command"], + services["mnt_dir"], + services["download_url"], + services["hypervisor"] + ) + ] + for c in cmds: + result = ssh.execute(c) + + res = str(result) + + # Unmount the Secondary storage + ssh.execute("umount %s" % (services["mnt_dir"])) + + if res.count("Successfully installed system VM template") == 1: + return + else: + raise Exception("Failed to download System Templates on Sec Storage") + return + + +def wait_for_ssvms(apiclient, zoneid, podid, interval=60): + """After setup wait for SSVMs to come Up""" + + time.sleep(interval) + timeout = 40 + while True: + list_ssvm_response = list_ssvms( + apiclient, + systemvmtype='secondarystoragevm', + zoneid=zoneid, + podid=podid + ) + ssvm = list_ssvm_response[0] + if ssvm.state != 'Running': + # Sleep to ensure SSVMs are Up and Running + time.sleep(interval) + timeout = timeout - 1 + elif ssvm.state == 'Running': + break + elif timeout == 0: + raise Exception("SSVM failed to come up") + break + + timeout = 40 + while True: + list_ssvm_response = list_ssvms( + apiclient, + systemvmtype='consoleproxy', + zoneid=zoneid, + podid=podid + ) + cpvm = list_ssvm_response[0] + if cpvm.state != 'Running': + # Sleep to ensure SSVMs are Up and Running + time.sleep(interval) + timeout = timeout - 1 + elif cpvm.state == 'Running': + break + elif timeout == 0: + raise Exception("CPVM failed to come up") + break + return + + +def get_builtin_template_info(apiclient, zoneid): + """Returns hypervisor specific infor for templates""" + + list_template_response = Template.list( + apiclient, + templatefilter='featured', + zoneid=zoneid, + ) + + for b_template in list_template_response: + if b_template.templatetype == 'BUILTIN': + break + + extract_response = Template.extract(apiclient, + b_template.id, + 'HTTP_DOWNLOAD', + zoneid) + + return extract_response.url, b_template.hypervisor, b_template.format + + +def download_builtin_templates(apiclient, zoneid, hypervisor, host, + linklocalip, interval=60): + """After setup wait till builtin templates are downloaded""" + + # Change IPTABLES Rules + get_process_status( + host["ipaddress"], + host["port"], + host["username"], + host["password"], + linklocalip, + "iptables -P INPUT ACCEPT" + ) + time.sleep(interval) + # Find the BUILTIN Templates for given Zone, Hypervisor + list_template_response = list_templates( + apiclient, + hypervisor=hypervisor, + zoneid=zoneid, + templatefilter='self' + ) + + if not isinstance(list_template_response, list): + raise Exception("Failed to download BUILTIN templates") + + # Ensure all BUILTIN templates are downloaded + templateid = None + for template in list_template_response: + if template.templatetype == "BUILTIN": + templateid = template.id + + # Sleep to ensure that template is in downloading state after adding + # Sec storage + time.sleep(interval) + while True: + template_response = list_templates( + apiclient, + id=templateid, + zoneid=zoneid, + templatefilter='self' + ) + template = template_response[0] + # If template is ready, + # template.status = Download Complete + # Downloading - x% Downloaded + # Error - Any other string + if template.status == 'Download Complete': + break + + elif 'Downloaded' in template.status: + time.sleep(interval) + + elif 'Installing' not in template.status: + raise Exception("ErrorInDownload") + + return + + +def update_resource_limit(apiclient, resourcetype, account=None, + domainid=None, max=None, projectid=None): + """Updates the resource limit to 'max' for given account""" + + cmd = updateResourceLimit.updateResourceLimitCmd() + cmd.resourcetype = resourcetype + if account: + cmd.account = account + if domainid: + cmd.domainid = domainid + if max: + cmd.max = max + if projectid: + cmd.projectid = projectid + apiclient.updateResourceLimit(cmd) + return + + +def list_os_types(apiclient, **kwargs): + """List all os types matching criteria""" + + cmd = listOsTypes.listOsTypesCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listOsTypes(cmd)) + + +def list_routers(apiclient, **kwargs): + """List all Routers matching criteria""" + + cmd = listRouters.listRoutersCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listRouters(cmd)) + + +def list_zones(apiclient, **kwargs): + """List all Zones matching criteria""" + + cmd = listZones.listZonesCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listZones(cmd)) + + +def list_networks(apiclient, **kwargs): + """List all Networks matching criteria""" + + cmd = listNetworks.listNetworksCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listNetworks(cmd)) + + +def list_clusters(apiclient, **kwargs): + """List all Clusters matching criteria""" + + cmd = listClusters.listClustersCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listClusters(cmd)) + + +def list_ssvms(apiclient, **kwargs): + """List all SSVMs matching criteria""" + + cmd = listSystemVms.listSystemVmsCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listSystemVms(cmd)) + + +def list_storage_pools(apiclient, **kwargs): + """List all storage pools matching criteria""" + + cmd = listStoragePools.listStoragePoolsCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listStoragePools(cmd)) + + +def list_virtual_machines(apiclient, **kwargs): + """List all VMs matching criteria""" + + cmd = listVirtualMachines.listVirtualMachinesCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listVirtualMachines(cmd)) + + +def list_hosts(apiclient, **kwargs): + """List all Hosts matching criteria""" + + cmd = listHosts.listHostsCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listHosts(cmd)) + + +def list_configurations(apiclient, **kwargs): + """List configuration with specified name""" + + cmd = listConfigurations.listConfigurationsCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listConfigurations(cmd)) + + +def list_publicIP(apiclient, **kwargs): + """List all Public IPs matching criteria""" + + cmd = listPublicIpAddresses.listPublicIpAddressesCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listPublicIpAddresses(cmd)) + + +def list_nat_rules(apiclient, **kwargs): + """List all NAT rules matching criteria""" + + cmd = listPortForwardingRules.listPortForwardingRulesCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listPortForwardingRules(cmd)) + + +def list_lb_rules(apiclient, **kwargs): + """List all Load balancing rules matching criteria""" + + cmd = listLoadBalancerRules.listLoadBalancerRulesCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listLoadBalancerRules(cmd)) + + +def list_lb_instances(apiclient, **kwargs): + """List all Load balancing instances matching criteria""" + + cmd = listLoadBalancerRuleInstances.listLoadBalancerRuleInstancesCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listLoadBalancerRuleInstances(cmd)) + + +def list_firewall_rules(apiclient, **kwargs): + """List all Firewall Rules matching criteria""" + + cmd = listFirewallRules.listFirewallRulesCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listFirewallRules(cmd)) + + +def list_volumes(apiclient, **kwargs): + """List all volumes matching criteria""" + + cmd = listVolumes.listVolumesCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listVolumes(cmd)) + + +def list_isos(apiclient, **kwargs): + """Lists all available ISO files.""" + + cmd = listIsos.listIsosCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listIsos(cmd)) + + +def list_snapshots(apiclient, **kwargs): + """List all snapshots matching criteria""" + + cmd = listSnapshots.listSnapshotsCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listSnapshots(cmd)) + + +def list_templates(apiclient, **kwargs): + """List all templates matching criteria""" + + cmd = listTemplates.listTemplatesCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listTemplates(cmd)) + + +def list_domains(apiclient, **kwargs): + """Lists domains""" + + cmd = listDomains.listDomainsCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listDomains(cmd)) + + +def list_accounts(apiclient, **kwargs): + """Lists accounts and provides detailed account information for + listed accounts""" + + cmd = listAccounts.listAccountsCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listAccounts(cmd)) + + +def list_users(apiclient, **kwargs): + """Lists users and provides detailed account information for + listed users""" + + cmd = listUsers.listUsersCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listUsers(cmd)) + + +def list_snapshot_policy(apiclient, **kwargs): + """Lists snapshot policies.""" + + cmd = listSnapshotPolicies.listSnapshotPoliciesCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listSnapshotPolicies(cmd)) + + +def list_events(apiclient, **kwargs): + """Lists events""" + + cmd = listEvents.listEventsCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listEvents(cmd)) + + +def list_disk_offering(apiclient, **kwargs): + """Lists all available disk offerings.""" + + cmd = listDiskOfferings.listDiskOfferingsCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listDiskOfferings(cmd)) + + +def list_service_offering(apiclient, **kwargs): + """Lists all available service offerings.""" + + cmd = listServiceOfferings.listServiceOfferingsCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listServiceOfferings(cmd)) + + +def list_vlan_ipranges(apiclient, **kwargs): + """Lists all VLAN IP ranges.""" + + cmd = listVlanIpRanges.listVlanIpRangesCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listVlanIpRanges(cmd)) + + +def list_usage_records(apiclient, **kwargs): + """Lists usage records for accounts""" + + cmd = listUsageRecords.listUsageRecordsCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listUsageRecords(cmd)) + + +def list_nw_service_prividers(apiclient, **kwargs): + """Lists Network service providers""" + + cmd = listNetworkServiceProviders.listNetworkServiceProvidersCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listNetworkServiceProviders(cmd)) + + +def list_virtual_router_elements(apiclient, **kwargs): + """Lists Virtual Router elements""" + + cmd = listVirtualRouterElements.listVirtualRouterElementsCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listVirtualRouterElements(cmd)) + + +def list_network_offerings(apiclient, **kwargs): + """Lists network offerings""" + + cmd = listNetworkOfferings.listNetworkOfferingsCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listNetworkOfferings(cmd)) + + +def list_resource_limits(apiclient, **kwargs): + """Lists resource limits""" + + cmd = listResourceLimits.listResourceLimitsCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listResourceLimits(cmd)) + + +def list_vpc_offerings(apiclient, **kwargs): + """ Lists VPC offerings """ + + cmd = listVPCOfferings.listVPCOfferingsCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listVPCOfferings(cmd)) + + +def update_resource_count(apiclient, domainid, accountid=None, + projectid=None, rtype=None): + """updates the resource count + 0 - VM + 1 - Public IP + 2 - Volume + 3 - Snapshot + 4 - Template + 5 - Projects + 6 - Network + 7 - VPC + 8 - CPUs + 9 - RAM + 10 - Primary (shared) storage (Volumes) + 11 - Secondary storage (Snapshots, Templates & ISOs) + """ + + Resources.updateCount(apiclient, + domainid=domainid, + account=accountid if accountid else None, + projectid=projectid if projectid else None, + resourcetype=rtype if rtype else None + ) + return + - - def find_suitable_host(apiclient, vm): ++def findSuitableHostForMigration(apiclient, vmid): + """Returns a suitable host for VM migration""" ++ suitableHost = None ++ try: ++ hosts = Host.listForMigration(apiclient, virtualmachineid=vmid, ++ ) ++ except Exception as e: ++ raise Exception("Exception while getting hosts list suitable for migration: %s" % e) + - hosts = Host.list(apiclient, - virtualmachineid=vm.id, - listall=True) ++ suitablehosts = [] ++ if isinstance(hosts, list) and len(hosts) > 0: ++ suitablehosts = [host for host in hosts if (str(host.resourcestate).lower() == "enabled"\ ++ and str(host.state).lower() == "up")] ++ if len(suitablehosts)>0: ++ suitableHost = suitablehosts[0] + - if isinstance(hosts, list): - assert len(hosts) > 0, "List host should return valid response" - else: - raise Exception("Exception: List host should return valid response") - return hosts[0] ++ return suitableHost + + +def get_resource_type(resource_id): + """Returns resource type""" + + lookup = {0: "VM", + 1: "Public IP", + 2: "Volume", + 3: "Snapshot", + 4: "Template", + 5: "Projects", + 6: "Network", + 7: "VPC", + 8: "CPUs", + 9: "RAM", + 10: "Primary (shared) storage (Volumes)", + 11: "Secondary storage (Snapshots, Templates & ISOs)" + } + + return lookup[resource_id] + + +def get_portable_ip_range_services(config): + """ Reads config values related to portable ip and fills up + services accordingly""" + + services = {} + attributeError = False + + if config.portableIpRange.startip: + services["startip"] = config.portableIpRange.startip + else: + attributeError = True + + if config.portableIpRange.endip: + services["endip"] = config.portableIpRange.endip + else: + attributeError = True + + if config.portableIpRange.netmask: + services["netmask"] = config.portableIpRange.netmask + else: + attributeError = True + + if config.portableIpRange.gateway: + services["gateway"] = config.portableIpRange.gateway + else: + attributeError = True + + if config.portableIpRange.vlan: + services["vlan"] = config.portableIpRange.vlan + + if attributeError: + services = None + + return services + + +def get_free_vlan(apiclient, zoneid): + """ + Find an unallocated VLAN outside the range allocated to the physical network. + + @note: This does not guarantee that the VLAN is available for use in + the deployment's network gear + @return: physical_network, shared_vlan_tag + """ + list_physical_networks_response = PhysicalNetwork.list( + apiclient, + zoneid=zoneid + ) + assert isinstance(list_physical_networks_response, list) + assert len( + list_physical_networks_response) > 0, "No physical networks found in zone %s" % zoneid + + physical_network = list_physical_networks_response[0] + + networks = list_networks(apiclient, zoneid=zoneid, type='Shared') + usedVlanIds = [] + + if isinstance(networks, list) and len(networks) > 0: + usedVlanIds = [int(nw.vlan) + for nw in networks if nw.vlan != "untagged"] + + if hasattr(physical_network, "vlan") is False: + while True: + shared_ntwk_vlan = random.randrange(1, 4095) + if shared_ntwk_vlan in usedVlanIds: + continue + else: + break + else: + vlans = xsplit(physical_network.vlan, ['-', ',']) + + assert len(vlans) > 0 + assert int(vlans[0]) < int( + vlans[-1]), "VLAN range %s was improperly split" % physical_network.vlan + + # Assuming random function will give different integer each time + retriesCount = 20 + + shared_ntwk_vlan = None + + while True: + + if retriesCount == 0: + break + + free_vlan = int(vlans[-1]) + random.randrange(1, 20) + + if free_vlan > 4095: + free_vlan = int(vlans[0]) - random.randrange(1, 20) + if free_vlan < 0 or (free_vlan in usedVlanIds): + retriesCount -= 1 + continue + else: + shared_ntwk_vlan = free_vlan + break + + return physical_network, shared_ntwk_vlan + + +def setNonContiguousVlanIds(apiclient, zoneid): + """ + Form the non contiguous ranges based on currently assigned range in physical network + """ + + NonContigVlanIdsAcquired = False + + list_physical_networks_response = PhysicalNetwork.list( + apiclient, + zoneid=zoneid + ) + assert isinstance(list_physical_networks_response, list) + assert len( + list_physical_networks_response) > 0, "No physical networks found in zone %s" % zoneid + + for physical_network in list_physical_networks_response: + + vlans = xsplit(physical_network.vlan, ['-', ',']) + + assert len(vlans) > 0 + assert int(vlans[0]) < int( + vlans[-1]), "VLAN range %s was improperly split" % physical_network.vlan + + # Keep some gap between existing vlan and the new vlans which we are going to add + # So that they are non contiguous + + non_contig_end_vlan_id = int(vlans[-1]) + 6 + non_contig_start_vlan_id = int(vlans[0]) - 6 + + # Form ranges which are consecutive to existing ranges but not immediately contiguous + # There should be gap in between existing range and new non contiguous + # ranage + + # If you can't add range after existing range, because it's crossing 4095, then + # select VLAN ids before the existing range such that they are greater than 0, and + # then add this non contiguoud range + vlan = {"partial_range": ["", ""], "full_range": ""} + + if non_contig_end_vlan_id < 4095: + vlan["partial_range"][0] = str( + non_contig_end_vlan_id - 4) + '-' + str(non_contig_end_vlan_id - 3) + vlan["partial_range"][1] = str( + non_contig_end_vlan_id - 1) + '-' + str(non_contig_end_vlan_id) + vlan["full_range"] = str( + non_contig_end_vlan_id - 4) + '-' + str(non_contig_end_vlan_id) + NonContigVlanIdsAcquired = True + + elif non_contig_start_vlan_id > 0: + vlan["partial_range"][0] = str( + non_contig_start_vlan_id) + '-' + str(non_contig_start_vlan_id + 1) + vlan["partial_range"][1] = str( + non_contig_start_vlan_id + 3) + '-' + str(non_contig_start_vlan_id + 4) + vlan["full_range"] = str( + non_contig_start_vlan_id) + '-' + str(non_contig_start_vlan_id + 4) + NonContigVlanIdsAcquired = True + + else: + NonContigVlanIdsAcquired = False + + # If failed to get relevant vlan ids, continue to next physical network + # else break from loop as we have hot the non contiguous vlan ids for + # the test purpose + + if not NonContigVlanIdsAcquired: + continue + else: + break + + # If even through looping from all existing physical networks, failed to get relevant non + # contiguous vlan ids, then fail the test case + + if not NonContigVlanIdsAcquired: + return None, None + + return physical_network, vlan ++ ++def is_public_ip_in_correct_state(apiclient, ipaddressid, state): ++ """ Check if the given IP is in the correct state (given) ++ and return True/False accordingly""" ++ retriesCount = 10 ++ while True: ++ portableips = PublicIPAddress.list(apiclient, id=ipaddressid) ++ assert validateList(portableips)[0] == PASS, "IPs list validation failed" ++ if str(portableips[0].state).lower() == state: ++ break ++ elif retriesCount == 0: ++ return False ++ else: ++ retriesCount -= 1 ++ time.sleep(60) ++ continue ++ return True ++ ++def setSharedNetworkParams(networkServices, range=20): ++ """Fill up the services dictionary for shared network using random subnet""" ++ ++ # @range: range decides the endip. Pass the range as "x" if you want the difference between the startip ++ # and endip as "x" ++ # Set the subnet number of shared networks randomly prior to execution ++ # of each test case to avoid overlapping of ip addresses ++ shared_network_subnet_number = random.randrange(1,254) ++ ++ networkServices["gateway"] = "172.16."+str(shared_network_subnet_number)+".1" ++ networkServices["startip"] = "172.16."+str(shared_network_subnet_number)+".2" ++ networkServices["endip"] = "172.16."+str(shared_network_subnet_number)+"."+str(range+1) ++ networkServices["netmask"] = "255.255.255.0" ++ return networkServices ++ ++def createEnabledNetworkOffering(apiclient, networkServices): ++ """Create and enable network offering according to the type ++ ++ @output: List, containing [ Result,Network Offering,Reason ] ++ Ist Argument('Result') : FAIL : If exception or assertion error occurs ++ PASS : If network offering ++ is created and enabled successfully ++ IInd Argument(Net Off) : Enabled network offering ++ In case of exception or ++ assertion error, it will be None ++ IIIrd Argument(Reason) : Reason for failure, ++ default to None ++ """ ++ try: ++ resultSet = [FAIL, None, None] ++ # Create network offering ++ network_offering = NetworkOffering.create(apiclient, networkServices, conservemode=False) ++ ++ # Update network offering state from disabled to enabled. ++ NetworkOffering.update(network_offering, apiclient, id=network_offering.id, ++ state="enabled") ++ except Exception as e: ++ resultSet[2] = e ++ return resultSet ++ return [PASS, network_offering, None] ++ ++def shouldTestBeSkipped(networkType, zoneType): ++ """Decide which test to skip, according to type of network and zone type""" ++ ++ # If network type is isolated or vpc and zone type is basic, then test should be skipped ++ skipIt = False ++ if ((networkType.lower() == str(ISOLATED_NETWORK).lower() or networkType.lower() == str(VPC_NETWORK).lower()) ++ and (zoneType.lower() == BASIC_ZONE)): ++ skipIt = True ++ return skipIt ++ ++def verifyNetworkState(apiclient, networkid, state): ++ """List networks and check if the network state matches the given state""" ++ try: ++ networks = Network.list(apiclient, id=networkid) ++ except Exception as e: ++ raise Exception("Failed while fetching network list with error: %s" % e) ++ assert validateList(networks)[0] == PASS, "Networks list validation failed, list is %s" % networks ++ assert str(networks[0].state).lower() == state, "network state should be %s, it is %s" % (state, networks[0].state) ++ return ++ ++def verifyComputeOfferingCreation(apiclient, computeofferingid): ++ """List Compute offerings by ID and verify that the offering exists""" ++ ++ cmd = listServiceOfferings.listServiceOfferingsCmd() ++ cmd.id = computeofferingid ++ serviceOfferings = None ++ try: ++ serviceOfferings = apiclient.listServiceOfferings(cmd) ++ except Exception: ++ return FAIL ++ if not (isinstance(serviceOfferings, list) and len(serviceOfferings) > 0): ++ return FAIL ++ return PASS ++ ++def createNetworkRulesForVM(apiclient, virtualmachine, ruletype, ++ account, networkruledata): ++ """Acquire IP, create Firewall and NAT/StaticNAT rule ++ (associating it with given vm) for that IP""" ++ ++ try: ++ public_ip = PublicIPAddress.create( ++ apiclient,accountid=account.name, ++ zoneid=virtualmachine.zoneid,domainid=account.domainid, ++ networkid=virtualmachine.nic[0].networkid) ++ ++ FireWallRule.create( ++ apiclient,ipaddressid=public_ip.ipaddress.id, ++ protocol='TCP', cidrlist=[networkruledata["fwrule"]["cidr"]], ++ startport=networkruledata["fwrule"]["startport"], ++ endport=networkruledata["fwrule"]["endport"] ++ ) ++ ++ if ruletype == NAT_RULE: ++ # Create NAT rule ++ NATRule.create(apiclient, virtualmachine, ++ networkruledata["natrule"],ipaddressid=public_ip.ipaddress.id, ++ networkid=virtualmachine.nic[0].networkid) ++ elif ruletype == STATIC_NAT_RULE: ++ # Enable Static NAT for VM ++ StaticNATRule.enable(apiclient,public_ip.ipaddress.id, ++ virtualmachine.id, networkid=virtualmachine.nic[0].networkid) ++ except Exception as e: ++ [FAIL, e] ++ return [PASS, public_ip]
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/0e223d67/tools/marvin/marvin/lib/utils.py ---------------------------------------------------------------------- diff --cc tools/marvin/marvin/lib/utils.py index 7617f90,0000000..cb5dcfb mode 100644,000000..100644 --- a/tools/marvin/marvin/lib/utils.py +++ b/tools/marvin/marvin/lib/utils.py @@@ -1,473 -1,0 +1,498 @@@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +"""Utilities functions +""" + +import marvin +import os +import time +import logging +import string +import random +import imaplib +import email +import socket +import urlparse +import datetime ++from marvin.cloudstackAPI import cloudstackAPIClient, listHosts, listRouters +from platform import system - from marvin.cloudstackAPI import cloudstackAPIClient, listHosts +from marvin.cloudstackException import GetDetailExceptionInfo +from marvin.sshClient import SshClient +from marvin.codes import ( + SUCCESS, + FAIL, + PASS, + MATCH_NOT_FOUND, + INVALID_INPUT, + EMPTY_LIST, + FAILED) + +def restart_mgmt_server(server): + """Restarts the management server""" + + try: + # Get the SSH client + ssh = is_server_ssh_ready( + server["ipaddress"], + server["port"], + server["username"], + server["password"], + ) + result = ssh.execute("/etc/init.d/cloud-management restart") + res = str(result) + # Server Stop - OK + # Server Start - OK + if res.count("OK") != 2: + raise ("ErrorInReboot!") + except Exception as e: + raise e + return + + +def fetch_latest_mail(services, from_mail): + """Fetch mail""" + + # Login to mail server to verify email + mail = imaplib.IMAP4_SSL(services["server"]) + mail.login( + services["email"], + services["password"] + ) + mail.list() + mail.select(services["folder"]) + date = (datetime.date.today() - datetime.timedelta(1)).strftime("%d-%b-%Y") + + result, data = mail.uid( + 'search', + None, + '(SENTSINCE {date} HEADER FROM "{mail}")'.format( + date=date, + mail=from_mail + ) + ) + # Return False if email is not present + if data == []: + return False + + latest_email_uid = data[0].split()[-1] + result, data = mail.uid('fetch', latest_email_uid, '(RFC822)') + raw_email = data[0][1] + email_message = email.message_from_string(raw_email) + result = get_first_text_block(email_message) + return result + + +def get_first_text_block(email_message_instance): + """fetches first text block from the mail""" + maintype = email_message_instance.get_content_maintype() + if maintype == 'multipart': + for part in email_message_instance.get_payload(): + if part.get_content_maintype() == 'text': + return part.get_payload() + elif maintype == 'text': + return email_message_instance.get_payload() + + +def random_gen(id=None, size=6, chars=string.ascii_uppercase + string.digits): + """Generate Random Strings of variable length""" + randomstr = ''.join(random.choice(chars) for x in range(size)) + if id: + return ''.join([id, '-', randomstr]) + return randomstr + + +def cleanup_resources(api_client, resources): + """Delete resources""" + for obj in resources: + obj.delete(api_client) + + +def is_server_ssh_ready(ipaddress, port, username, password, retries=20, retryinterv=30, timeout=10.0, keyPairFileLocation=None): + ''' + @Name: is_server_ssh_ready + @Input: timeout: tcp connection timeout flag, + others information need to be added + @Output:object for SshClient + Name of the function is little misnomer and is not + verifying anything as such mentioned + ''' + + try: + ssh = SshClient( + host=ipaddress, + port=port, + user=username, + passwd=password, + keyPairFiles=keyPairFileLocation, + retries=retries, + delay=retryinterv, + timeout=timeout) + except Exception, e: + raise Exception("SSH connection has Failed. Waited %ss. Error is %s" % (retries * retryinterv, str(e))) + else: + return ssh + + +def format_volume_to_ext3(ssh_client, device="/dev/sda"): + """Format attached storage to ext3 fs""" + cmds = [ + "echo -e 'n\np\n1\n\n\nw' | fdisk %s" % device, + "mkfs.ext3 %s1" % device, + ] + for c in cmds: + ssh_client.execute(c) + + +def fetch_api_client(config_file='datacenterCfg'): + """Fetch the Cloudstack API Client""" + config = marvin.configGenerator.get_setup_config(config_file) + mgt = config.mgtSvr[0] + testClientLogger = logging.getLogger("testClient") + asyncTimeout = 3600 + return cloudstackAPIClient.CloudStackAPIClient( + marvin.cloudstackConnection.cloudConnection( + mgt, + asyncTimeout, + testClientLogger + ) + ) + +def get_host_credentials(config, hostip): + """Get login information for a host `hostip` (ipv4) from marvin's `config` + + @return the tuple username, password for the host else raise keyerror""" + for zone in config.zones: + for pod in zone.pods: + for cluster in pod.clusters: + for host in cluster.hosts: + if str(host.url).startswith('http'): + hostname = urlparse.urlsplit(str(host.url)).netloc + else: + hostname = str(host.url) + try: + if socket.getfqdn(hostip) == socket.getfqdn(hostname): + return host.username, host.password + except socket.error, e: + raise Exception("Unresolvable host %s error is %s" % (hostip, e)) + raise KeyError("Please provide the marvin configuration file with credentials to your hosts") + + +def get_process_status(hostip, port, username, password, linklocalip, process, hypervisor=None): + """Double hop and returns a process status""" + + #SSH to the machine + ssh = SshClient(hostip, port, username, password) - if str(hypervisor).lower() == 'vmware': ++ if (str(hypervisor).lower() == 'vmware' ++ or str(hypervisor).lower() == 'hyperv'): + ssh_command = "ssh -i /var/cloudstack/management/.ssh/id_rsa -ostricthostkeychecking=no " + else: + ssh_command = "ssh -i ~/.ssh/id_rsa.cloud -ostricthostkeychecking=no " + + ssh_command = ssh_command +\ + "-oUserKnownHostsFile=/dev/null -p 3922 %s %s" % ( + linklocalip, + process) + + # Double hop into router + timeout = 5 + # Ensure the SSH login is successful + while True: + res = ssh.execute(ssh_command) + + if res[0] != "Host key verification failed.": + break + elif timeout == 0: + break + + time.sleep(5) + timeout = timeout - 1 + return res + + +def isAlmostEqual(first_digit, second_digit, range=0): + digits_equal_within_range = False + + try: + if ((first_digit - range) < second_digit < (first_digit + range)): + digits_equal_within_range = True + except Exception as e: + raise e + return digits_equal_within_range + + +def xsplit(txt, seps): + """ + Split a string in `txt` by list of delimiters in `seps` + @param txt: string to split + @param seps: list of separators + @return: list of split units + """ + default_sep = seps[0] + for sep in seps[1:]: # we skip seps[0] because that's the default separator + txt = txt.replace(sep, default_sep) + return [i.strip() for i in txt.split(default_sep)] + +def get_hypervisor_type(apiclient): + + """Return the hypervisor type of the hosts in setup""" + + cmd = listHosts.listHostsCmd() + cmd.type = 'Routing' + cmd.listall = True + hosts = apiclient.listHosts(cmd) + hosts_list_validation_result = validateList(hosts) + assert hosts_list_validation_result[0] == PASS, "host list validation failed" + return hosts_list_validation_result[1].hypervisor + +def is_snapshot_on_nfs(apiclient, dbconn, config, zoneid, snapshotid): + """ + Checks whether a snapshot with id (not UUID) `snapshotid` is present on the nfs storage + + @param apiclient: api client connection + @param @dbconn: connection to the cloudstack db + @param config: marvin configuration file + @param zoneid: uuid of the zone on which the secondary nfs storage pool is mounted + @param snapshotid: uuid of the snapshot + @return: True if snapshot is found, False otherwise + """ + # snapshot extension to be appended to the snapshot path obtained from db + snapshot_extensions = {"vmware": ".ovf", + "kvm": "", + "xenserver": ".vhd", + "simulator":""} + + qresultset = dbconn.execute( + "select id from snapshots where uuid = '%s';" \ + % str(snapshotid) + ) + if len(qresultset) == 0: + raise Exception( + "No snapshot found in cloudstack with id %s" % snapshotid) + + + snapshotid = qresultset[0][0] + qresultset = dbconn.execute( + "select install_path,store_id from snapshot_store_ref where snapshot_id='%s' and store_role='Image';" % snapshotid + ) + + assert isinstance(qresultset, list), "Invalid db query response for snapshot %s" % snapshotid + + if len(qresultset) == 0: + #Snapshot does not exist + return False + + from base import ImageStore + #pass store_id to get the exact storage pool where snapshot is stored + secondaryStores = ImageStore.list(apiclient, zoneid=zoneid, id=int(qresultset[0][1])) + + assert isinstance(secondaryStores, list), "Not a valid response for listImageStores" + assert len(secondaryStores) != 0, "No image stores found in zone %s" % zoneid + + secondaryStore = secondaryStores[0] + + if str(secondaryStore.providername).lower() != "nfs": + raise Exception( + "is_snapshot_on_nfs works only against nfs secondary storage. found %s" % str(secondaryStore.providername)) + + hypervisor = get_hypervisor_type(apiclient) + # append snapshot extension based on hypervisor, to the snapshot path + snapshotPath = str(qresultset[0][0]) + snapshot_extensions[str(hypervisor).lower()] + + nfsurl = secondaryStore.url + from urllib2 import urlparse + parse_url = urlparse.urlsplit(nfsurl, scheme='nfs') + host, path = parse_url.netloc, parse_url.path + + if not config.mgtSvr: + raise Exception("Your marvin configuration does not contain mgmt server credentials") + mgtSvr, user, passwd = config.mgtSvr[0].mgtSvrIp, config.mgtSvr[0].user, config.mgtSvr[0].passwd + + try: + ssh_client = SshClient( + mgtSvr, + 22, + user, + passwd + ) + cmds = [ + "mkdir -p %s /mnt/tmp", + "mount -t %s %s%s /mnt/tmp" % ( + 'nfs', + host, + path, + ), + "test -f %s && echo 'snapshot exists'" % ( + os.path.join("/mnt/tmp", snapshotPath) + ), + ] + + for c in cmds: + result = ssh_client.execute(c) + + # Unmount the Sec Storage + cmds = [ + "cd", + "umount /mnt/tmp", + ] + for c in cmds: + ssh_client.execute(c) + except Exception as e: + raise Exception("SSH failed for management server: %s - %s" % + (config.mgtSvr[0].mgtSvrIp, e)) + return 'snapshot exists' in result + +def validateList(inp): + """ + @name: validateList + @Description: 1. A utility function to validate + whether the input passed is a list + 2. The list is empty or not + 3. If it is list and not empty, return PASS and first element + 4. If not reason for FAIL + @Input: Input to be validated + @output: List, containing [ Result,FirstElement,Reason ] + Ist Argument('Result') : FAIL : If it is not a list + If it is list but empty + PASS : If it is list and not empty + IInd Argument('FirstElement'): If it is list and not empty, + then first element + in it, default to None + IIIrd Argument( 'Reason' ): Reason for failure ( FAIL ), + default to None. + INVALID_INPUT + EMPTY_LIST + """ + ret = [FAIL, None, None] + if inp is None: + ret[2] = INVALID_INPUT + return ret + if not isinstance(inp, list): + ret[2] = INVALID_INPUT + return ret + if len(inp) == 0: + ret[2] = EMPTY_LIST + return ret + return [PASS, inp[0], None] + +def verifyElementInList(inp, toverify, responsevar=None, pos=0): + ''' + @name: verifyElementInList + @Description: + 1. A utility function to validate + whether the input passed is a list. + The list is empty or not. + If it is list and not empty, verify + whether a given element is there in that list or not + at a given pos + @Input: + I : Input to be verified whether its a list or not + II : Element to verify whether it exists in the list + III : variable name in response object to verify + default to None, if None, we will verify for the complete + first element EX: state of response object object + IV : Position in the list at which the input element to verify + default to 0 + @output: List, containing [ Result,Reason ] + Ist Argument('Result') : FAIL : If it is not a list + If it is list but empty + PASS : If it is list and not empty + and matching element was found + IIrd Argument( 'Reason' ): Reason for failure ( FAIL ), + default to None. + INVALID_INPUT + EMPTY_LIST + MATCH_NOT_FOUND + ''' + if toverify is None or toverify == '' \ + or pos is None or pos < -1 or pos == '': + return [FAIL, INVALID_INPUT] + out = validateList(inp) + if out[0] == FAIL: + return [FAIL, out[2]] + if len(inp) > pos: + if responsevar is None: + if inp[pos] == toverify: + return [PASS, None] + else: + if responsevar in inp[pos].__dict__ and getattr(inp[pos], responsevar) == toverify: + return [PASS, None] + else: + return [FAIL, MATCH_NOT_FOUND] + else: + return [FAIL, MATCH_NOT_FOUND] + - +def checkVolumeSize(ssh_handle=None, + volume_name="/dev/sda", + cmd_inp="/sbin/fdisk -l | grep Disk", + size_to_verify=0): + ''' + @Name : getDiskUsage + @Desc : provides facility to verify the volume size against the size to verify + @Input: 1. ssh_handle : machine against which to execute the disk size cmd + 2. volume_name : The name of the volume against which to verify the size + 3. cmd_inp : Input command used to veify the size + 4. size_to_verify: size against which to compare. + @Output: Returns FAILED in case of an issue, else SUCCESS + ''' + try: + if ssh_handle is None or cmd_inp is None or volume_name is None: + return INVALID_INPUT + + cmd = cmd_inp + ''' + Retrieve the cmd output + ''' + if system().lower() != "windows": + fdisk_output = ssh_handle.runCommand(cmd_inp) + if fdisk_output["status"] != SUCCESS: + return FAILED + temp_out = fdisk_output["stdout"] + for line in temp_out.split("\n"): + if volume_name in line: + parts = line.split() + if str(parts[-2]) == str(size_to_verify): + return [SUCCESS,str(parts[-2])] + return [FAILED,"Volume Not Found"] + except Exception, e: + print "\n Exception Occurred under getDiskUsage: " \ + "%s" %GetDetailExceptionInfo(e) + return [FAILED,GetDetailExceptionInfo(e)] ++ ++ ++def verifyRouterState(apiclient, routerid, allowedstates): ++ """List the router and verify that its state is in allowed states ++ @output: List, containing [Result, Reason] ++ Ist Argument ('Result'): FAIL: If router state is not ++ in allowed states ++ PASS: If router state is in ++ allowed states""" ++ ++ try: ++ cmd = listRouters.listRoutersCmd() ++ cmd.id = routerid ++ cmd.listall = True ++ routers = apiclient.listRouters(cmd) ++ except Exception as e: ++ return [FAIL, e] ++ listvalidationresult = validateList(routers) ++ if listvalidationresult[0] == FAIL: ++ return [FAIL, listvalidationresult[2]] ++ if routers[0].redundantstate not in allowedstates: ++ return [FAIL, "Redundant state of the router should be in %s but is %s" % ++ (allowedstates, routers[0].redundantstate)] ++ return [PASS, None] ++
