# ex: set expandtab softtabstop=4 shiftwidth=4: -*- cpy-indent-level: 4; indent-tabs-mode: nil -*-
#
# Copyright (C) 2010  Contributor
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the EU DataGrid Software License.  You should
# have received a copy of the license with this program, and the
# license is published at
# http://eu-datagrid.web.cern.ch/eu-datagrid/license.html.
#
# THE FOLLOWING DISCLAIMER APPLIES TO ALL SOFTWARE CODE AND OTHER
# MATERIALS CONTRIBUTED IN CONNECTION WITH THIS PROGRAM.
#
# THIS SOFTWARE IS LICENSED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE AND ANY WARRANTY OF NON-INFRINGEMENT, ARE
# DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. THIS
# SOFTWARE MAY BE REDISTRIBUTED TO OTHERS ONLY BY EFFECTIVELY USING
# THIS OR ANOTHER EQUIVALENT DISCLAIMER AS WELL AS ANY OTHER LICENSE
# TERMS THAT MAY APPLY.
"""Contains the logic for `aq update personality`."""

from aquilon.server.broker import BrokerCommand
from aquilon.aqdb.model import Personality, PersonalityESXClusterInfo, Cluster
from aquilon.aqdb.model.cluster import restricted_builtins
from aquilon.exceptions_ import ArgumentError


class CommandUpdatePersonality(BrokerCommand):

    required_parameters = ["personality", "archetype"]

    def render(self, session, personality, archetype, vmhost_capacity_function,
               **arguments):
        dbpersona = Personality.get_unique(session, name=personality,
                                           archetype=archetype, compel=True)

        if vmhost_capacity_function:
            if not "esx" in dbpersona.cluster_infos:
                dbpersona.cluster_infos["esx"] = PersonalityESXClusterInfo()

            # Basic sanity tests to see if the function works
            try:
                global_vars = {'__builtins__': restricted_builtins}
                local_vars = {'memory': 10}
                capacity = eval(vmhost_capacity_function, global_vars,
                              local_vars)
            except Exception, err:
                raise ArgumentError("Failed to evaluate the function: %s" % err)
            if not isinstance(capacity, dict):
                raise ArgumentError("The function should return a dictonary.")
            for name, value in capacity.items():
                if not isinstance(name, str) or (not isinstance(value, int) and
                                                 not isinstance(value, float)):
                    raise ArgumentError("The function should return a dictionary "
                                        "with all keys being strings, and all "
                                        "values being numbers.")

            # TODO: Should this be mandatory? It is for now.
            if "memory" not in capacity:
                raise ArgumentError("The memory constraint is missing from "
                                    "the returned dictionary.")

            dbpersona.cluster_infos["esx"].vmhost_capacity_function = vmhost_capacity_function
        elif vmhost_capacity_function == "":
            if "esx" in dbpersona.cluster_infos:
                dbpersona.cluster_infos["esx"] = None

        session.flush()
        session.refresh(dbpersona)

        q = session.query(Cluster)
        q = q.with_polymorphic("*")
        q = q.filter_by(personality=dbpersona)
        clusters = q.all()
        for cluster in clusters:
            cluster.validate()
        return

@@ -1587,16 +1587,59 @@
 y%60.%22%22%22%0A%0A
+from sqlalchemy.orm import joinedload_all%0A%0A
 from aqu
@@ -3996,16 +3996,240 @@
 ic(%22*%22)%0A
+        # The validation will touch all member hosts/machines, so better to load%0A        # them upfront%0A        q = q.options(joinedload_all('_hosts.host.machine'))%0A        q = q.options(joinedload_all('_machines.machine'))%0A
         
@@ -4267,16 +4267,16 @@
 ersona)%0A
-
         
@@ -4294,16 +4294,38 @@
 q.all()%0A
+        failures = %5B%5D%0A
         
@@ -4349,16 +4349,37 @@
 usters:%0A
+            try:%0A    
         
@@ -4401,16 +4401,268 @@
 idate()%0A
+            except ArgumentError, err:%0A                failures.append(err.message)%0A        if len(failures):%0A            raise ArgumentError(%22Validation failed for the following %22%0A                                %22clusters:%5Cn%25s%22 %25 %22%5Cn%22.join(failures))%0A
         

Improve query performance