diff --git a/rally/ui/templates/ci/index.mako b/rally/ui/templates/ci/index.mako index 966672da05..a0dd0b88a1 100644 --- a/rally/ui/templates/ci/index.mako +++ b/rally/ui/templates/ci/index.mako @@ -50,6 +50,7 @@
  • Benchmarking logs console.html
  • Logs of all services logs/
  • Rally files rally-plot/ +
  • Changes in resources rally-plot/resources_diff.txt

    Job results, in different formats

    diff --git a/tests/ci/osresources.py b/tests/ci/osresources.py new file mode 100755 index 0000000000..12cc218947 --- /dev/null +++ b/tests/ci/osresources.py @@ -0,0 +1,292 @@ +# All Rights Reserved. +# +# Licensed 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. + + +"""List and compare most used OpenStack cloud resources.""" + + +import argparse +import json +import subprocess +import sys + +from rally.common.plugin import discover +from rally import consts +from rally import osclients + + +class ResourceManager(object): + + REQUIRED_SERVICE = None + REPR_KEYS = ("id", "name", "tenant_id", "zone", "zoneName", "pool") + + def __init__(self, clients): + self.clients = clients + + def is_available(self): + if self.REQUIRED_SERVICE: + return self.REQUIRED_SERVICE in self.clients.services().values() + return True + + @property + def client(self): + return getattr(self.clients, self.__class__.__name__.lower())() + + def get_resources(self): + all_resources = [] + cls = self.__class__.__name__.lower() + for prop in dir(self): + if not prop.startswith("list_"): + continue + f = getattr(self, prop) + resources = f() or [] + resource_name = prop[5:][:-1] + + for res in resources: + res_repr = [] + for key in self.REPR_KEYS + (resource_name,): + if isinstance(res, dict): + value = res.get(key) + else: + value = getattr(res, key, None) + if value: + res_repr.append("%s:%s" % (key, value)) + if not res_repr: + raise ValueError("Failed to represent resource %r" % res) + + all_resources.append( + "%s %s %s" % (cls, resource_name, " ".join(res_repr))) + return all_resources + + +class Keystone(ResourceManager): + + def list_users(self): + return self.client.users.list() + + def list_tenants(self): + return self.client.tenants.list() + + def list_roles(self): + return self.client.roles.list() + + +class Nova(ResourceManager): + + def list_flavors(self): + return self.client.flavors.list() + + def list_floating_ip_pools(self): + return self.client.floating_ip_pools.list() + + def list_floating_ips(self): + return self.client.floating_ips.list() + + def list_images(self): + return self.client.images.list() + + def list_keypairs(self): + return self.client.keypairs.list() + + def list_networks(self): + return self.client.networks.list() + + def list_security_groups(self): + return self.client.security_groups.list( + search_opts={"all_tenants": True}) + + def list_servers(self): + return self.client.servers.list( + search_opts={"all_tenants": True}) + + def list_services(self): + return self.client.services.list() + + def list_availability_zones(self): + return self.client.availability_zones.list() + + +class Neutron(ResourceManager): + + REQUIRED_SERVICE = consts.Service.NEUTRON + + def has_extension(self, name): + extensions = self.client.list_extensions().get("extensions", []) + return any(ext.get("alias") == name for ext in extensions) + + def list_networks(self): + return self.client.list_networks()["networks"] + + def list_subnets(self): + return self.client.list_subnets()["subnets"] + + def list_routers(self): + return self.client.list_routers()["routers"] + + def list_ports(self): + return self.client.list_ports()["ports"] + + def list_floatingips(self): + return self.client.list_floatingips()["floatingips"] + + def list_security_groups(self): + return self.client.list_security_groups()["security_groups"] + + def list_health_monitors(self): + if self.has_extension("lbaas"): + return self.client.list_health_monitors()["health_monitors"] + + def list_pools(self): + if self.has_extension("lbaas"): + return self.client.list_pools()["pools"] + + def list_vips(self): + if self.has_extension("lbaas"): + return self.client.list_vips()["vips"] + + +class Glance(ResourceManager): + + def list_images(self): + return self.client.images.list() + + +class Heat(ResourceManager): + + def list_resource_types(self): + return self.client.resource_types.list() + + def list_stacks(self): + return self.client.stacks.list() + + +class Cinder(ResourceManager): + + def list_availability_zones(self): + return self.client.availability_zones.list() + + def list_backups(self): + return self.client.backups.list() + + def list_volume_snapshots(self): + return self.client.volume_snapshots.list() + + def list_volume_types(self): + return self.client.volume_types.list() + + def list_volumes(self): + return self.client.volumes.list( + search_opts={"all_tenants": True}) + + +class CloudResources(object): + """List and compare cloud resources. + + resources = CloudResources(auth_url=..., ...) + saved_list = resources.list() + + # Do something with the cloud ... + + changes = resources.compare(saved_list) + has_changed = any(changes) + removed, added = changes + """ + + def __init__(self, **kwargs): + endpoint = osclients.objects.Endpoint(**kwargs) + self.clients = osclients.Clients(endpoint) + + def _deduplicate(self, lst): + """Change list duplicates to make all items unique. + + >>> resources._deduplicate(["a", "b", "c", "b", "b"]) + >>> ['a', 'b', 'c', 'b (duplicate 1)', 'b (duplicate 2)' + """ + deduplicated_list = [] + for value in lst: + if value in deduplicated_list: + ctr = 0 + try_value = value + while try_value in deduplicated_list: + ctr += 1 + try_value = "%s (duplicate %i)" % (value, ctr) + value = try_value + deduplicated_list.append(value) + return deduplicated_list + + def list(self): + managers_classes = discover.itersubclasses(ResourceManager) + resources = [] + for cls in managers_classes: + manager = cls(self.clients) + if manager.is_available(): + resources.extend(manager.get_resources()) + return sorted(self._deduplicate(resources)) + + def compare(self, with_list): + saved_resources = set(with_list) + current_resources = set(self.list()) + removed = saved_resources - current_resources + added = current_resources - saved_resources + + return sorted(list(removed)), sorted(list(added)) + + +def main(): + + parser = argparse.ArgumentParser( + description=("Save list of OpenStack cloud resources or compare " + "with previously saved list.")) + parser.add_argument("--credentials", + type=argparse.FileType("r"), + metavar="", + help="cloud credentials in JSON format") + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument("--dump-list", + type=argparse.FileType("w"), + metavar="", + help="dump resources to given file in JSON format") + group.add_argument("--compare-with-list", + type=argparse.FileType("r"), + metavar="", + help=("compare current resources with a list from " + "given JSON file")) + args = parser.parse_args() + + if args.credentials: + config = json.load(args.credentials) + else: + config = json.loads(subprocess.check_output(["rally", "deployment", + "config"])) + config.update(config.pop("admin")) + del config["type"] + + resources = CloudResources(**config) + + if args.dump_list: + resources_list = resources.list() + json.dump(resources_list, args.dump_list, indent=2) + elif args.compare_with_list: + given_list = json.load(args.compare_with_list) + changes = resources.compare(with_list=given_list) + removed, added = changes + sys.stdout.write( + json.dumps({"removed": removed, "added": added}, indent=2)) + if any(changes): + return 0 # `1' will fail gate job + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/ci/rally-gate.sh b/tests/ci/rally-gate.sh index 271578b379..d2749cde85 100755 --- a/tests/ci/rally-gate.sh +++ b/tests/ci/rally-gate.sh @@ -60,6 +60,9 @@ rally show networks rally show secgroups rally show keypairs +python $BASE/new/rally/tests/ci/osresources.py\ + --dump-list resources_at_start.txt + rally -v --rally-debug task start --task $TASK $TASK_ARGS mkdir -p rally-plot/extra @@ -75,3 +78,8 @@ gzip -9 rally-plot/detailed_with_iterations.txt rally task report --out rally-plot/results.html gzip -9 rally-plot/results.html rally task sla_check | tee rally-plot/sla.txt + +python $BASE/new/rally/tests/ci/osresources.py\ + --compare-with-list resources_at_start.txt\ + | gzip > rally-plot/resources_diff.txt.gz +cp resources_at_start.txt rally-plot/