source: ccsd/trunk/crcnetd/modules/ccs_asset.py @ 1248

Last change on this file since 1248 was 1248, checked in by mglb1, 7 years ago

Let any authenticated user retrieve asset details

  • Property svn:keywords set to Id
File size: 81.9 KB
Line 
1# Copyright (C) 2006  The University of Waikato
2#
3# This file is part of crcnetd - CRCnet Configuration System Daemon
4#
5# Asset Module - Provides classes to manipulate assets in the system
6#
7# Author:       Matt Brown <matt@crc.net.nz>
8# Version:      $Id$
9#
10# crcnetd is free software; you can redistribute it and/or modify it under the
11# terms of the GNU General Public License version 2 as published by the Free
12# Software Foundation.
13#
14# crcnetd is distributed in the hope that it will be useful, but WITHOUT ANY
15# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17# details.
18#
19# You should have received a copy of the GNU General Public License along with
20# crcnetd; if not, write to the Free Software Foundation, Inc.,
21# 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
22from twisted.web import resource, server
23import os.path
24import re
25from OpenSSL import crypto
26
27from crcnetd._utils.ccsd_common import *
28from crcnetd._utils.ccsd_log import *
29from crcnetd._utils.ccsd_events import *
30from crcnetd._utils.ccsd_session import getSession, getSessionE
31from crcnetd._utils.ccsd_server import exportViaXMLRPC, registerResource
32from crcnetd._utils.ccsd_ca import ccs_ca, getCertificateParameters, \
33        CERT_PARAM_NAMES, REVOKE_SUPERSEDED
34from ccs_site import getSiteName
35
36ccs_mod_type = CCSD_SERVER
37DEFAULT_CURRENCY = "NZD"
38
39class ccs_asset_error(ccsd_error):
40    pass
41
42#####################################################################
43# Assets
44#####################################################################
45class ccs_asset(ccs_class):
46    """Provides an abstract interface to an asset in the CRCnet Configuration
47    System.
48   
49    The members of this class loosely map to the fields of the asset table
50    in the database.
51    """
52   
53    def __init__(self, session_id, asset_id):
54        """Initialises a new class for a specified asset.
55       
56        The specified session must be valid and have appropriate access to
57        the database for the tasks you intend to perform with the class. All
58        database access / configuration manipulation triggered by this
59        instance will pass through the specified session.
60        """
61
62        self._errMsg = ""
63        self._commit = 0
64        self._csInit = ""
65       
66        session = getSession(session_id)
67        if session is None:
68            raise ccs_asset_error("Invalid session id")
69        self._session_id = session_id
70       
71        # See if the specified asset id makes sense
72        sql = "SELECT * FROM asset WHERE asset_id=%s"
73        res = session.query(sql, (asset_id))
74        if len(res) < 1:
75            raise ccs_asset_error("Invalid asset. Unable to retrieve " 
76                "details")
77       
78        # Store details
79        self.asset_id = asset_id
80        self._properties = res[0]
81
82    @registerEvent("assetDetailsUpdated")
83    @exportViaXMLRPC(SESSION_RW, AUTH_ASSET_MANAGER, True)
84    def updateAssetDetail(self, data_name, value):
85        """Updates an asset in the database"""
86        session = getSessionE(self._session_id)
87       
88        # Check it is a valid value
89        if data_name not in ["description", "serial_no", "date_purchased", \
90                "currency", "price", "supplier", "enabled", "notes"]:
91            raise ccs_asset_error("Invalid asset data value!")
92        # Check the value of the data being updated
93        asset = {}
94        asset[data_name] = value
95        res =  _validateAsset(self._session_id, asset, False)
96       
97        # If a changeset is not already active, start one for this batch
98        self._forceChangeset("Updated details of asset #%s" % self.asset_id, \
99                "asset")
100
101        # Do the update
102        sql = "UPDATE asset SET %s=%%s WHERE asset_id=%%s" % data_name
103        session.execute(sql, (value, self.asset_id))
104           
105        # Record the change
106        sql = "INSERT INTO asset_event (asset_id, event_type, username, " \
107                "data1, data2) VALUES (%s, %s, %s, %s, %s)"
108        p = (self.asset_id, ASSET_EVENT_DETAILS_UPDATED, session.username, \
109                data_name, value)
110        session.execute(sql, p)
111
112        # Raise the event
113        triggerEvent(self._session_id, "assetDetailsUpdated", \
114                asset_id=self.asset_id)
115       
116        return self.returnSuccess()
117
118    @registerEvent("assetLocationChanged")
119    @exportViaXMLRPC(SESSION_RW, AUTH_ASSET_MANAGER, True, "moveAssetToSite")
120    def moveToSite(self, site_id):
121        """Updates the asset's location to the specified site"""
122
123        session = getSessionE(self._session_id)
124       
125        # Ensure the following changes are grouped together
126        self._forceChangeset("Moving asset #%s to site %s" % \
127                (self.asset_id, getSiteName(self._session_id, site_id)), \
128                "asset")
129   
130        # Update the asset location table
131        sql = "UPDATE asset_location SET site_id=%s, attached_to=NULL, " \
132                "location_updated=NOW() WHERE asset_id=%s"
133        p = (site_id, self.asset_id)
134        session.execute(sql, p)
135
136        # Record the event
137        sql = "INSERT INTO asset_event (asset_id, event_type, username, " \
138                "data1) VALUES (%s, %s, %s, %s)"
139        p = (self.asset_id, ASSET_EVENT_LOCATION_CHANGED, session.username, \
140                site_id)
141        session.execute(sql, p)
142   
143        # Raise the event
144        triggerEvent(session.session_id, "assetLocationChanged", \
145                asset_id=self.asset_id)
146       
147        return self.returnSuccess()
148   
149    @registerEvent("assetLocationChanged")
150    @exportViaXMLRPC(SESSION_RW, AUTH_ASSET_MANAGER, True, "attachAssetTo")
151    def attachTo(self, asset_id):
152        """Attaches this asset to the specified asset"""
153       
154        session = getSessionE(self._session_id)
155       
156        # Ensure the following changes are grouped together
157        self._forceChangeset("Attaching asset #%s to asset #%s" % \
158                (self.asset_id, asset_id), "asset")
159   
160        # Update the asset location table
161        sql = "UPDATE asset_location SET site_id=NULL, attached_to=%s, " \
162                "location_updated=NOW() WHERE asset_id=%s"
163        p = (asset_id, self.asset_id)
164        session.execute(sql, p)
165
166        # Record the event
167        sql = "INSERT INTO asset_event (asset_id, event_type, username, " \
168                "data1) VALUES (%s, %s, %s, %s)"
169        p = (self.asset_id, ASSET_EVENT_ATTACHED, session.username, \
170                asset_id)
171        session.execute(sql, p)
172   
173        # Raise the event
174        triggerEvent(session.session_id, "assetLocationChanged", \
175                asset_id=self.asset_id)
176       
177        return self.returnSuccess()
178   
179    @registerEvent("assetLocationChanged")
180    @exportViaXMLRPC(SESSION_RW, AUTH_ASSET_MANAGER, True, "assignAssetTo")
181    def assignTo(self, thing_name):
182        """Attaches this asset to a named thing (usually a person)"""
183       
184        session = getSessionE(self._session_id)
185       
186        # Check that we have a site called "Assigned Assets"
187        sql = "SELECT site_id FROM site WHERE location='" \
188                "Assigned Assets'"
189        assigned_site = session.getCountOf(sql, ())
190        if assigned_site <= 0 :
191            raise ccs_asset_error("Cannot assign asset! " \
192                    "No 'Assigned Assets' location")
193        if len(thing_name.strip()) <= 0:
194            raise ccs_asset_error("You must specify the name of what the " \
195                    "asset is being assigned to")
196
197        # Ensure the following changes are grouped together
198        self._forceChangeset("Assigning asset #%s to %s" % \
199                (self.asset_id, thing_name), "asset")
200   
201        # Update the asset location table
202        sql = "UPDATE asset_location SET site_id=%s, attached_to=NULL, " \
203                "location_updated=NOW() WHERE asset_id=%s"
204        p = (assigned_site, self.asset_id)
205        session.execute(sql, p)
206
207        # Record the event
208        sql = "INSERT INTO asset_event (asset_id, event_type, username, " \
209                "data1) VALUES (%s, %s, %s, %s)"
210        p = (self.asset_id, ASSET_EVENT_ASSIGNED, session.username, \
211                thing_name)
212        session.execute(sql, p)
213   
214        # Raise the event
215        triggerEvent(session.session_id, "assetLocationChanged", \
216                asset_id=self.asset_id)
217       
218        return self.returnSuccess()
219       
220    def moveToStock(self):
221        """Updates the asset's location to the default stock site"""
222 
223        session = getSessionE(self._session_id)
224       
225        # Ensure the following changes are grouped together
226        self._forceChangeset("Moving asset #%s to stock" % (self.asset_id), \
227                "asset")
228   
229        # Update the asset location table
230        sql = "UPDATE asset_location SET site_id=(SELECT site_id FROM " \
231                "asset_stock_location WHERE default_location='t'), " \
232                "attached_to=NULL, location_updated=NOW() WHERE asset_id=%s"
233        p = (self.asset_id)
234        session.execute(sql, p)
235
236        # Get the stock location
237        sql = "SELECT site_id FROM asset_stock_location WHERE " \
238                "default_location='t'"
239        default_site = session.getCountOf(sql, ())
240       
241        # Record the event
242        sql = "INSERT INTO asset_event (asset_id, event_type, username, " \
243                "data1) VALUES (%s, %s, %s, %s)"
244        p = (self.asset_id, ASSET_EVENT_LOCATION_CHANGED, session.username, \
245                default_site)
246        session.execute(sql, p)
247   
248        # Raise the event
249        triggerEvent(session.session_id, "assetLocationChanged", \
250                asset_id=self.asset_id)
251       
252        return self.returnSuccess()
253   
254    def supportsFunction(self, function):
255        """Returns true if this asset supports the specified function"""
256
257        session = getSessionE(self._session_id)
258       
259        return session.getCountOf("SELECT count(*) FROM asset a, " \
260            "asset_type_map am WHERE a.asset_type_id=" \
261            "am.asset_type_id AND am.asset_function=%s AND " \
262            "a.asset_id=%s", (function, self.asset_id))
263
264    def availableForHost(self, site_id):
265        """Returns true if this asset can be a host at the specified site
266       
267        If site_id is -1 then Stock is assumed.
268        """
269        session = getSessionE(self._session_id)
270
271        # Check if specified asset is availale for use at a host
272        res = session.getCountOf("SELECT count(*) FROM assets_avail WHERE " \
273                "asset_id=%s", (self.asset_id))
274        if res == 1:
275            return True
276
277        # Check site specific availability
278        if site_id!=-1:
279            res = session.getCountOf("SELECT count(*) FROM " \
280                    "site_host_assets_avail " \
281                    "WHERE site_id=%s AND asset_id=%s", \
282                    (site_id, self.asset_id))
283        if res == 1:
284            return True
285       
286        # Not available, must be in use
287        return False
288   
289    @registerEvent("subassetAdded")
290    @exportViaXMLRPC(SESSION_RW, AUTH_ASSET_MANAGER, True)
291    def addSubasset(self, at_subasset_id, sadata):
292        """Adds a new subasset to an asset"""
293        session = getSessionE(self._session_id)
294
295        # Validate parameters
296        res = _isValidAssetTypeSubassetId(self._session_id, at_subasset_id)
297       
298        # Check this asset is:
299        # - allowed this subasset
300        # - doesn't have it already
301        sql = "SELECT ats.asset_type_subasset_id, ats.subasset_type_id " \
302                "FROM asset_type_subasset ats, asset a WHERE " \
303                "a.asset_type_id=ats.asset_type_id AND a.asset_id=%s " \
304                "AND ats.asset_type_subasset_id not in (SELECT " \
305                "asset_type_subasset_id FROM subasset WHERE asset_id=%s)"
306        res = session.query(sql, (self.asset_id, self.asset_id))
307        subasset_type_id = -1
308        for row in res:
309            if int(row["asset_type_subasset_id"]) == int(at_subasset_id):
310                subasset_type_id = row["subasset_type_id"]
311                break
312        if subasset_type_id == -1:
313            raise ccs_asset_error("Cannot add subasset of specified type " \
314                    "to this asset!")
315           
316        # Check all the properties
317        properties = getLinkedProperties(self._session_id, subasset_type_id)
318        schema = {}
319        schema["properties"] = {}
320        props = {}
321        for prop in properties:
322            subasset_property_id = prop["subasset_property_id"]
323            props[subasset_property_id] = prop
324        schema["properties"] = props
325        _validateSubassetProperties(sadata["properties"], schema)
326       
327        # If a changeset is not already active, start one for this batch
328        self._forceChangeset("Added subasset to asset #%s" % self.asset_id, \
329                "asset")
330
331        # Add the subasset
332        sql = "INSERT INTO subasset (asset_id, asset_type_subasset_id, " \
333                    "enabled) VALUES (%s, %s, %s)"
334        p = (self.asset_id, at_subasset_id, sadata["enabled"])
335        session.execute(sql, p)
336
337        # Get the new subasset_id
338        sql = "SELECT currval('subasset_subasset_id_seq') as subasset_id"
339        res = session.query(sql, ())
340        subasset_id = res[0]["subasset_id"]
341       
342        # Add the property data
343        for subasset_property_id,value in sadata["properties"].items():
344            sql = "INSERT INTO asset_data (subasset_property_id, " \
345                    "subasset_id, value) VALUES (%s, %s, %s)"
346            p = (subasset_property_id, subasset_id, str(value))
347            session.execute(sql, p)
348       
349        # Add event
350        # XXX: This should probably be functionalised
351        sql = "INSERT INTO asset_event (asset_id, event_type, username, " \
352                "data1) VALUES (%s, %s, %s, %s)"
353        session.execute(sql, (self.asset_id, ASSET_EVENT_SUBASSET_ADDED, \
354                session.username, subasset_id))
355       
356        # Raise the event
357        triggerEvent(self._session_id, "subassetAdded", \
358                asset_id=self.asset_id, subasset_id=subasset_id)
359
360        return subasset_id
361
362    @exportViaXMLRPC(SESSION_RO, AUTH_ASSET_MANAGER, True)
363    def getAttachedAssets(self):
364        """Returns a list of assets that are attached to this asset"""
365        session = getSessionE(self._session_id)
366
367        return session.query("SELECT a.* FROM asset a, asset_location al " \
368                "WHERE a.asset_id=al.asset_id AND al.attached_to=%s", \
369                (self.asset_id))
370       
371    def getTemplateVariables(self):
372       
373        variables = {}
374       
375        for key,value in filter_keys(self._properties).items():
376            if key.startswith("asset"):
377                variables[key] = value
378            else:
379                variables["asset_%s" % key] = value
380
381        return variables
382
383    def getMAC(self):
384        """Returns the MAC address of the asset
385
386        This method is only available for assets that may be used as a host.
387        We simply return the MAC address of the eth0 interface as the device's
388        MAC address
389
390        This is used by many modules as a unique key for the asset that will
391        not change irrespective of its asset ID. It is also easily discoverable
392        via the network.
393        """
394       
395        # Check this asset can be used as a host
396        if not self.supportsFunction("host"):
397            raise ccs_asset_error("Cannot retrieve eth0 MAC address for a " \
398                    "non host asset")
399       
400        # Look for the MAC address
401        mac = None
402        asset = getAsset(self._session_id, self.asset_id)
403        for id,subasset in asset["subassets"].items():
404            if subasset["name"] != "eth0":
405                continue
406            for pid, prop in subasset["properties"].items():
407                if prop["description"] == "MAC Address":
408                    mac = prop["value"]
409                    break
410            break   
411        if mac is None or mac == "":
412            raise ccs_asset_error("No eth0 MAC address found!")
413       
414        return mac.lower()
415   
416    @exportViaXMLRPC(SESSION_RW, AUTH_ASSET_MANAGER, True, "getAssetCert")
417    def getCert(self, csr):
418        """Signs the specified request to generate a certificate for the asset
419
420        The request is only signed if
421        * The asset in question is able to be used as a host asset
422        * The certificate parameters match the certificate authority
423        * The CN parameter must be entirely in lower case
424        * The CN parameter matches the eth0 mac address of the asset
425       
426        If any previous certificate has been allocated to the asset it is first
427        revoked and a new CRL is generated before the new asset certificate is
428        issued.
429        """
430       
431        # Check this asset can be used as a host
432        if not self.supportsFunction("host"):
433            raise ccs_asset_error("Cannot allocate certificate to a " \
434                    "non host asset")
435
436        # Check the certificate parameters
437        params = getCertificateParameters()
438        try:
439            req = crypto.load_certificate_request(crypto.FILETYPE_PEM, csr)
440            subj = req.get_subject()
441            for p in CERT_PARAM_NAMES:
442                if p == "CN" or p == "emailAddress":
443                    # Don't need to verify these
444                    continue
445                if getattr(subj, p) != params[p]:
446                    raise ccs_asset_error("Certificate Request does not " \
447                            "match CA parameter: %s (%s)" % (p, params[p]))
448        except ccs_asset_error: raise
449        except:
450            log_error("Unexpected error validating asset certificate " \
451                    "parameters", sys.exc_info())
452            raise ccs_asset_error("Could not validate certificate parameters")
453
454        # Check the CN parameter matches the lowercase eth0 mac address
455        mac = self.getMAC()
456        if getattr(subj, "CN") != mac:
457            raise ccs_asset_error("Certificate Request CN parameter does " \
458                    "not match asset MAC address!")
459
460        # Checks all pass, grab a CA instance
461        try:
462            ca = ccs_ca(self._session_id)
463        except:
464            log_error("Unexpected error initialising CA instance", \
465                    sys.exc_info())
466            raise ccs_asset_error("Could not obtain CA instance")
467       
468        # revoke any previous key
469        try:
470            # Check any certificates with matching CN's are in the revoked
471            # state
472            ocerts = ca.findByCN(getattr(subj,"CN"))
473            for cert in ocerts:
474                if cert["state"] != "R":
475                    ca.revoke(cert["serial"], REVOKE_SUPERSEDED, \
476                            "new asset certificate requested")
477        except:
478            log_error("Unexpected error dealing with old asset certificate", \
479                    sys.exc_info())
480            raise ccs_asset_error("Could not deal with old asset certificate")
481       
482        # Get the CA and sign the request
483        try:
484            cert = ca.signReq(csr)
485            cobj = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
486            serial = cobj.get_serial_number()
487            log_info("Certificate %s assigned to asset #%s" % \
488                    (serial, self.asset_id))
489        except:
490            log_error("Unexpected error signing asset certificate", \
491                    sys.exc_info())
492            raise ccs_asset_error("Could not sign certificate")
493
494        return cert
495
496@registerEvent("assetAdded")
497@exportViaXMLRPC(SESSION_RW, AUTH_ASSET_MANAGER)
498def addAsset(session_id, asset):
499    """Adds a new asset to the database"""
500    session = getSessionE(session_id)
501
502    # Validate asset parameters
503    _validateAsset(session_id, asset)
504   
505    # Set defaults
506    if "serial_no" not in asset:
507        asset["serial_no"] = ""
508    if "currency" not in asset:
509        asset["currency"] = DEFAULT_CURRENCY
510    if "price" not in asset:
511        asset["price"] = float(0)
512    else:
513        asset["price"] = float(asset["price"])
514    if "supplier" not in asset:
515        asset["supplier"] = ""
516    if "notes" not in asset:
517        asset["notes"] = ""
518       
519    # Check the appropriate subasset data is present
520    res = _validateAssetSubasset(session_id, asset)
521   
522    # If a changeset is not already active, start one for this batch
523    commit = 0
524    if session.changeset == 0:
525        session.begin("Created Subasset", "add_asset")
526        commit = 1
527   
528    # Insert the primary asset record
529    sql = "INSERT INTO asset (asset_type_id, description, serial_no, " \
530            "date_purchased, currency, price, supplier, enabled, notes) " \
531            "VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)"
532    p = (asset["asset_type"], asset["description"], str(asset["serial_no"]), \
533            asset["date_purchased"], asset["currency"], asset["price"], 
534            asset["supplier"], asset["enabled"], asset["notes"]) 
535    session.execute(sql, p)
536
537    # Get the new asset_id
538    sql = "SELECT currval('asset_asset_id_seq') as asset_id"
539    res = session.query(sql, ())
540    asset_id = res[0]["asset_id"]
541   
542    # Create the subassets
543    for asset_type_subasset_id,subasset in asset["subassets"].items():
544        sql = "INSERT INTO subasset (asset_id, asset_type_subasset_id, " \
545                "enabled) VALUES (%s, %s, %s)"
546        p = (asset_id, asset_type_subasset_id, subasset["enabled"])
547        session.execute(sql, p)
548        # Get the new subasset_id
549        sql = "SELECT currval('subasset_subasset_id_seq') as subasset_id"
550        res = session.query(sql, ())
551        subasset_id = res[0]["subasset_id"]
552        # Add the property data
553        for subasset_property_id,value in subasset["properties"].items():
554            sql = "INSERT INTO asset_data (subasset_property_id, " \
555                    "subasset_id, value) VALUES (%s, %s, %s)"
556            p = (subasset_property_id, subasset_id, str(value))
557            session.execute(sql, p)
558   
559    # Setup add record and initial location
560    # XXX: This should probably be functionalised
561    sql = "INSERT INTO asset_event (asset_id, event_type, username, " \
562            "data1) VALUES (%s, %s, %s, %s)"
563    session.execute(sql, (asset_id, ASSET_EVENT_CREATED, \
564            session.username, ""))
565   
566    sql = "SELECT site_id FROM asset_stock_location WHERE default_location='t'"
567    default_site = session.getCountOf(sql, ())     
568    sql = "INSERT INTO asset_location (asset_id, site_id) VALUES (%s, %s)"
569    session.execute(sql, (asset_id, default_site))
570
571    # If we started a changeset, then finish it off
572    if commit == 1:
573        session.commit()
574   
575    # Raise the event
576    triggerEvent(session_id, "assetAdded", asset_id=asset_id)
577
578    return asset_id
579   
580@registerEvent("assetAdded")
581@exportViaXMLRPC(SESSION_RW, AUTH_ASSET_MANAGER)
582def addSoekris(session_id, asset):
583    """Adds one or more soekris assets to the database
584   
585    Soekris details should be passed in as a dictionary containing the
586    normal asset details and another dictionary called isoekris with the
587    invidual soekris details. The soekris are inserted into the asset database
588    in numerical order based on their key in the isoekris dictionary.
589    """
590    session = getSessionE(session_id)
591
592    # Validate asset parameters
593    _validateAsset(session_id, asset)
594   
595    # Set defaults
596    if "currency" not in asset:
597        asset["currency"] = DEFAULT_CURRENCY
598    if "price" not in asset:
599        asset["price"] = float(0)
600    else:
601        asset["price"] = float(asset["price"])
602    if "supplier" not in asset:
603        asset["supplier"] = ""
604    if "notes" not in asset:
605        asset["notes"] = ""
606       
607    # If a changeset is not already active, start one for this batch
608    commit = 0
609    if session.changeset == 0:
610        session.begin("Creating Soekris Assets", "addSoekris")
611        commit = 1
612       
613    sql = "SELECT site_id FROM asset_stock_location WHERE " \
614            "default_location='t'"
615    default_site = session.getCountOf(sql, ())     
616
617    # Insert each asset, try and keep them in order
618    keys = asset["isoekris"].keys()
619    keys.sort(key=int)
620    n=0
621    for idx in keys:
622        isoekris = asset["isoekris"][idx]
623
624        # Calculate mac address and serial number details
625        if isoekris["serial"] != "":
626            isoekris["eth0mac"] = getSoekrisMac(isoekris["serial"], 0);
627        elif isoekris["mac"] != "":
628            isoekris["serial"] = getSoekrisSerial(isoekris["mac"])
629            isoekris["eth0mac"] = isoekris["mac"]
630        else:
631            # Invalid data, skip this record
632            continue
633       
634        # Calculate a few more macs incase we need them
635        isoekris["eth1mac"] = getSoekrisMac(isoekris["serial"], 1);
636        isoekris["eth2mac"] = getSoekrisMac(isoekris["serial"], 2);
637        isoekris["eth3mac"] = getSoekrisMac(isoekris["serial"], 3);
638       
639        # Insert the primary asset record
640        sql = "INSERT INTO asset (asset_type_id, description, serial_no, " \
641                "date_purchased, currency, price, supplier, enabled, notes) " \
642                "VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)"
643        p = (asset["asset_type"], asset["description"], \
644                str(isoekris["serial"]), asset["date_purchased"], \
645                asset["currency"], asset["price"], asset["supplier"], \
646                asset["enabled"], asset["notes"]) 
647        session.execute(sql, p)
648       
649        # Get the new asset_id
650        sql = "SELECT currval('asset_asset_id_seq') as asset_id"
651        res = session.query(sql, ())
652        asset_id = res[0]["asset_id"]
653       
654        # Create the subassets
655        schema = getAssetSchema(session_id, asset["asset_type"])
656        for asset_type_subasset_id,subasset in schema.items():
657            sql = "INSERT INTO subasset (asset_id, asset_type_subasset_id, " \
658                    "enabled) VALUES (%s, %s, 't')"
659            p = (asset_id, asset_type_subasset_id)
660            session.execute(sql, p)
661            # Get the new subasset_id
662            sql = "SELECT currval('subasset_subasset_id_seq') as subasset_id"
663            res = session.query(sql, ())
664            subasset_id = res[0]["subasset_id"]
665            # Add the property data
666            for subasset_property_id,data in subasset["properties"].items():
667                value = ""
668                if subasset["name"].startswith("eth") and \
669                        data["description"].startswith("MAC"):
670                    # If this is a MAC address property, look up the mac
671                    # address
672                    kname = "%smac" % subasset["name"]
673                    if kname in isoekris.keys():
674                        value = isoekris[kname]
675                if value == "":
676                    # If we haven't got a value already use the default
677                    value = data["default_value"]
678                sql = "INSERT INTO asset_data (subasset_property_id, " \
679                        "subasset_id, value) VALUES (%s, %s, %s)"
680                p = (subasset_property_id, subasset_id, str(value))
681                session.execute(sql, p)
682   
683        # Setup add record and initial location
684        # XXX: This should probably be functionalised
685        sql = "INSERT INTO asset_event (asset_id, event_type, username, " \
686                "data1) VALUES (%s, %s, %s, %s)"
687        session.execute(sql, (asset_id, ASSET_EVENT_CREATED, \
688                session.username, ""))
689       
690        sql = "INSERT INTO asset_location (asset_id, site_id) VALUES (%s, %s)"
691        session.execute(sql, (asset_id, default_site))
692       
693        # Raise the event
694        triggerEvent(session_id, "assetAdded", asset_id=asset_id)
695
696        n+=1
697       
698    # If we started a changeset, then finish it off
699    if commit == 1:
700        session.commit()
701
702    if n == 0:
703        raise ccs_asset_error("No soekris were able to be created!")
704
705    return n
706
707@registerEvent("assetAdded")
708@exportViaXMLRPC(SESSION_RW, AUTH_ASSET_MANAGER)
709def addWirelessCards(session_id, asset):
710    """Adds one or more wireless card assets to the database
711   
712    Card details should be passed in as a dictionary containing the
713    normal asset details and another dictionary called icards with the
714    invidual card details. The cards are inserted into the asset database
715    in numerical order based on their key in the icards dictionary.
716    """
717    session = getSessionE(session_id)
718
719    # Validate asset parameters
720    _validateAsset(session_id, asset)
721   
722    # Set defaults
723    if "currency" not in asset:
724        asset["currency"] = DEFAULT_CURRENCY
725    if "price" not in asset:
726        asset["price"] = float(0)
727    else:
728        asset["price"] = float(asset["price"])
729    if "supplier" not in asset:
730        asset["supplier"] = ""
731    if "notes" not in asset:
732        asset["notes"] = ""
733       
734    # If a changeset is not already active, start one for this batch
735    commit = 0
736    if session.changeset == 0:
737        session.begin("Creating Wireless Card Assets", "addWirelessCard")
738        commit = 1
739       
740    sql = "SELECT site_id FROM asset_stock_location WHERE " \
741            "default_location='t'"
742    default_site = session.getCountOf(sql, ())     
743
744    # Insert each asset, try and keep them in order
745    keys = asset["icards"].keys()
746    keys.sort(key=int)
747    n=0
748    for idx in keys:
749        icard = asset["icards"][idx]
750       
751        # Insert the primary asset record
752        sql = "INSERT INTO asset (asset_type_id, description, serial_no, " \
753                "date_purchased, currency, price, supplier, enabled, notes) " \
754                "VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)"
755        p = (asset["asset_type"], asset["description"], \
756                str(icard["serial"]), asset["date_purchased"], \
757                asset["currency"], asset["price"], asset["supplier"], \
758                asset["enabled"], asset["notes"]) 
759        session.execute(sql, p)
760       
761        # Get the new asset_id
762        sql = "SELECT currval('asset_asset_id_seq') as asset_id"
763        res = session.query(sql, ())
764        asset_id = res[0]["asset_id"]
765       
766        # Create the subassets
767        schema = getAssetSchema(session_id, asset["asset_type"])
768        for asset_type_subasset_id,subasset in schema.items():
769            sql = "INSERT INTO subasset (asset_id, asset_type_subasset_id, " \
770                    "enabled) VALUES (%s, %s, 't')"
771            p = (asset_id, asset_type_subasset_id)
772            session.execute(sql, p)
773            # Get the new subasset_id
774            sql = "SELECT currval('subasset_subasset_id_seq') as subasset_id"
775            res = session.query(sql, ())
776            subasset_id = res[0]["subasset_id"]
777            # Add the property data
778            for subasset_property_id,data in subasset["properties"].items():
779                value = ""
780                if subasset["name"].startswith("wireless") and \
781                        data["description"].startswith("MAC"):
782                    # If this is a MAC address property, look up the mac
783                    # address
784                    value = icard["mac"]
785                if value == "":
786                    # If we haven't got a value already use the default
787                    value = data["default_value"]
788                sql = "INSERT INTO asset_data (subasset_property_id, " \
789                        "subasset_id, value) VALUES (%s, %s, %s)"
790                p = (subasset_property_id, subasset_id, str(value))
791                session.execute(sql, p)
792   
793        # Setup add record and initial location
794        # XXX: This should probably be functionalised
795        sql = "INSERT INTO asset_event (asset_id, event_type, username, " \
796                "data1) VALUES (%s, %s, %s, %s)"
797        session.execute(sql, (asset_id, ASSET_EVENT_CREATED, \
798                session.username, ""))
799       
800        sql = "INSERT INTO asset_location (asset_id, site_id) VALUES (%s, %s)"
801        session.execute(sql, (asset_id, default_site))
802       
803        # Raise the event
804        triggerEvent(session_id, "assetAdded", asset_id=asset_id)
805
806        n+=1
807       
808    # If we started a changeset, then finish it off
809    if commit == 1:
810        session.commit()
811
812    if n == 0:
813        raise ccs_asset_error("No wireless cards were able to be created!")
814
815    return n
816   
817@exportViaXMLRPC(SESSION_RO, AUTH_ASSET_MANAGER)
818def getAssetList(session_id, asset_type_id=-1, site_id=-1):
819    """Returns a list of assets"""
820    session = getSessionE(session_id)
821   
822    params = []
823    sql = "SELECT a.asset_id, a.description, a.asset_type_id, a.enabled, " \
824            "al.site_id, al.attached_to, date_part('epoch', " \
825            "al.location_updated) as location_updated, " \
826            "COALESCE(s.location, 'Attached to Asset #' || " \
827            "al.attached_to) AS location FROM asset a, " \
828            "asset_location al LEFT JOIN site s ON " \
829            "al.site_id=s.site_id WHERE a.asset_id=al.asset_id"
830    if asset_type_id != -1:
831        sql = "%s AND a.asset_type_id=%%s" % sql
832        params.append(asset_type_id)
833    if site_id != -1:
834        sql = "%s AND al.site_id=%%s" % sql
835        params.append(site_id)
836    sql = "%s ORDER BY asset_type_id, asset_id" % sql
837    res = session.query(sql, params)
838    if len(res) < 1:
839        return {}
840
841    return res
842
843@exportViaXMLRPC(SESSION_RO, AUTH_ASSET_MANAGER)
844def getAssetSchema(session_id, asset_type_id):
845    """Returns the schema to be used for assets of this type."""
846    session = getSessionE(session_id)
847   
848    schema = {}
849   
850    subassets = getLinkedSubassets(session_id, asset_type_id)
851    for subasset in subassets:
852        asset_type_subasset_id = subasset["asset_type_subasset_id"]
853        schema[asset_type_subasset_id] = subasset
854        properties = getLinkedProperties(session_id, \
855                subasset["subasset_type_id"])
856        props = {}
857        for prop in properties:
858           subasset_property_id = prop["subasset_property_id"]
859           props[subasset_property_id] = prop
860        schema[asset_type_subasset_id]["properties"] = props
861
862    return schema
863
864@exportViaXMLRPC(SESSION_RO, AUTH_ASSET_MANAGER)
865def getAssetLocation(session_id, asset_id):
866    session = getSessionE(session_id)
867
868    sql = "SELECT asset_id, site_id, attached_to, " \
869            "date_part('epoch', location_updated) AS location_updated FROM " \
870            "asset_location WHERE asset_id=%s"
871    res = session.query(sql, (asset_id))
872
873    if len(res) != 1:
874        raise ccs_asset_error("No location information available!")
875   
876    # Check that we have a site called "Assigned Assets"
877    sql = "SELECT site_id FROM site WHERE location='" \
878            "Assigned Assets'"
879    assigned_site = session.getCountOf(sql, ())
880
881    if res[0]["site_id"] != "":
882        if assigned_site > 0 and res[0]["site_id"] == assigned_site:
883            sql = "SELECT data1 FROM asset_event WHERE event_type=%s " \
884                    "AND asset_id=%s ORDER BY timestamp DESC LIMIT 1"
885            thing_name = session.getCountOf(sql, \
886                    (ASSET_EVENT_ASSIGNED, asset_id))
887            res[0]["description"] = "Assigned to %s" % thing_name
888        else:
889            res[0]["description"] = getSiteName(session_id, res[0]["site_id"])
890    else:
891        res[0]["description"] = "Attached to asset #%s" % res[0]["attached_to"]
892   
893    return res[0]
894
895@exportViaXMLRPC(SESSION_RO, AUTH_ASSET_MANAGER)
896def getAssetHistory(session_id, asset_id):
897    """Retrieves all the history information about this asset"""
898    session = getSessionE(session_id)
899
900    sql = "SELECT ae.asset_event_id, ae.asset_id, date_part('epoch', " \
901            "ae.timestamp) as timestamp, et.description, ae.username, " \
902            "ae.data1, ae.data2, ae.event_type FROM asset_event ae, " \
903            "event_type et WHERE ae.event_type=et.event_type_id AND " \
904            "ae.asset_id=%s ORDER BY timestamp DESC"
905    res = session.query(sql, (asset_id))
906   
907    # Turn the data fields into something readable
908    res2 = []
909    for event in res:
910        if event["event_type"] == ASSET_EVENT_LOCATION_CHANGED:
911            event["event_desc"] = "To '%s'" % (getSiteName(session_id, \
912                    event["data1"]))
913        elif event["event_type"] == ASSET_EVENT_ATTACHED:
914            event["event_desc"] = "To asset #%s" % event["data1"]
915        elif event["event_type"] == ASSET_EVENT_ASSIGNED:
916            event["event_desc"] = "To %s" % event["data1"]
917        elif event["event_type"] == ASSET_EVENT_DETAILS_UPDATED:
918            event["event_desc"] = "%s set to '%s'" % \
919                    (event["data1"], event["data2"])
920        elif event["event_type"] == ASSET_EVENT_PROPERTY_UPDATED:
921            parts = event["data1"].split(":")
922            subasset_id = parts[0]
923            subasset_property_id = parts[1]
924            if subasset_property_id == "enabled":
925                event["event_desc"] = "%s enabled set to '%s'" % \
926                    (getSubassetName(session_id, subasset_id), \
927                    event["data2"])
928            else:
929                event["event_desc"] = "%s %s set to '%s'" % \
930                    (getSubassetName(session_id, subasset_id), \
931                    _getSubassetPropertyDescription(session_id, \
932                    subasset_property_id), event["data2"])
933        elif event["event_type"] == ASSET_EVENT_SUBASSET_ADDED:
934            event["event_desc"] = getSubassetName(session_id, event["data1"])
935        else:
936            event["event_desc"] = "&nbsp;"
937        res2.append(event)
938       
939    return res2
940
941@exportViaXMLRPC(SESSION_RO, AUTH_ASSET_MANAGER)
942def getAssetByMac(session_id, mac):
943    """Retrieves all the information about an asset based on a MAC address"""
944    session = getSessionE(session_id)
945
946    # Validate MAC address
947    if not isValidMAC(mac):
948        raise ccs_asset_error("Invalid MAC address!")
949   
950    sql = "SELECT asset_id FROM asset_macs WHERE mac=upper(%s)"
951    res = session.query(sql, (mac.strip()))
952    if len(res) < 1:
953        raise ccs_asset_error("Specified MAC address not known!")
954
955    asset_id = res[0]["asset_id"]
956
957    return getAsset(session_id, asset_id)
958
959@exportViaXMLRPC(SESSION_RO, AUTH_ASSET_MANAGER)
960def getAssetDescription(session_id, asset_id):
961    """Retrieves the description of the specified asset"""
962    session = getSessionE(session_id)
963   
964    validateAssetId(session_id, asset_id)
965   
966    res = session.query("SELECT description FROM asset WHERE asset_id=%s", \
967            (asset_id))
968    if len(res)<1:
969        return ""
970    return res[0]["description"]
971
972@exportViaXMLRPC(SESSION_RO, AUTH_AUTHENTICATED)
973def getAsset(session_id, asset_id):
974    """Retrieves all the information about this asset from the database"""
975    session = getSessionE(session_id)
976
977    # Get the basic asset data
978    sql = "SELECT a.*, date_part('epoch', a.date_purchased) AS " \
979            "date_purchased_ts, at.description AS " \
980            "asset_type_description FROM asset a, " \
981            "asset_type at WHERE a.asset_type_id=at.asset_type_id AND " \
982            "a.asset_id=%s"
983    res = session.query(sql, (asset_id))
984   
985    if len(res) != 1:
986        raise ccs_asset_error("Invalid asset result returned!")
987   
988    asset = res[0]
989   
990    # Get some location data
991    ldata = getAssetLocation(session_id, asset_id)
992    asset["site_id"] = ldata["site_id"]
993    asset["attached_to"] = ldata["attached_to"]
994    asset["location"] = ldata["description"]
995    asset["location_updated"] = ldata["location_updated"]
996
997    # Now get the required subassets (and optional subassets with data)
998    sql = "SELECT sat.description, ats.name, ats.required, " \
999            "ats.asset_type_subasset_id, sa.subasset_id, sa.enabled " \
1000            "FROM asset_type_subasset ats " \
1001            "LEFT JOIN subasset sa ON sa.asset_type_subasset_id=" \
1002            "ats.asset_type_subasset_id, subasset_type sat WHERE " \
1003            "ats.subasset_type_id=sat.subasset_type_id AND " \
1004            "sa.asset_id=%s"
1005    res = session.query(sql, (asset_id))
1006    subassets = {}
1007    subasset_list = ""
1008    for subasset in res:
1009        # Get the properties for this subasset
1010        sql = "SELECT sap.subasset_id, sap.description, sap.required, " \
1011            "sap.default_value, sap.subasset_property_id, ad.value FROM " \
1012            "subasset_properties sap LEFT JOIN asset_data ad USING " \
1013            "(subasset_id, subasset_property_id) WHERE sap.subasset_id=%s"
1014        res2 = session.query(sql, (subasset["subasset_id"]))
1015        properties = {}
1016        proplist = ""
1017        for data in res2:
1018            properties[data["subasset_property_id"]] = data
1019            proplist = "%s %s" % (proplist, data["subasset_property_id"])
1020        subasset["properties"] = properties
1021        subasset["property_list"] = proplist
1022        subassets[subasset["subasset_id"]] = subasset
1023        subasset_list = "%s %s" % (subasset_list, subasset["subasset_id"])
1024       
1025    asset["subassets"] = subassets
1026    asset["subasset_list"] = subasset_list
1027
1028    # Other available optional subassets
1029    sql = "SELECT ats.asset_type_subasset_id, sat.description, ats.name, " \
1030            "ats.subasset_type_id FROM asset_type_subasset ats, asset a, " \
1031            "asset_type at, subasset_type sat WHERE " \
1032            "a.asset_id=%s AND a.asset_type_id=at.asset_type_id AND " \
1033            "ats.asset_type_id=at.asset_type_id AND ats.subasset_type_id=" \
1034            "sat.subasset_type_id AND ats.asset_type_subasset_id NOT IN " \
1035            "(SELECT asset_type_subasset_id FROM subasset WHERE " \
1036            "asset_id=%s) AND ats.required='f'"
1037    p = (asset_id, asset_id)
1038    res = session.query(sql, p)
1039
1040    subassets = {}
1041    for subasset in res:
1042        # Get the properties for this optional subasset
1043        sql = "SELECT sap.subasset_property_id, sap.asset_property_id, " \
1044                "sap.required, sap.default_value, sat.description FROM " \
1045                "subasset_property sap, subasset_type sat WHERE " \
1046                "sap.subasset_type_id=%s AND " \
1047                "sap.subasset_type_id=sat.subasset_type_id"
1048        res2 = session.query(sql, (subasset["subasset_type_id"]))
1049        properties = {}
1050        for data in res2:
1051            properties[data["subasset_property_id"]] = data         
1052        subasset["properties"] = properties
1053        subassets[subasset["asset_type_subasset_id"]] = subasset
1054             
1055    asset["optionalsubassets"] = subassets
1056
1057    return asset
1058
1059def validateAssetId(session_id, asset_id):
1060    """Checks the specified asset_id is OK"""
1061    session = getSessionE(session_id)
1062
1063    res = session.getCountOf("SELECT count(*) FROM asset " \
1064            "WHERE asset_id=%s", (asset_id))
1065    if res == 1:
1066        return
1067
1068    raise ccs_asset_error("Invalid Asset ID: Not Found")
1069
1070def _validateAsset(session_id, asset, required=True):
1071    """Validates the asset parameters"""
1072   
1073    # Check the parameters for the main asset
1074    if "asset_type" in asset:
1075        res = _isValidAssetType(session_id, asset["asset_type"])
1076    if "serialno" in asset:
1077        if len(str(asset["serialno"]))>32:
1078            raise ccs_asset_error("Serial No. must be less than 32 " \
1079                    "characters!")
1080    if ("description" not in asset or len(asset["description"])==0):
1081        if required:
1082            raise ccs_asset_error("Description not specified!")
1083    else:
1084        if len(asset["description"])>64:
1085            raise ccs_asset_error("Description must be less than 64 " \
1086                    "characters!")
1087    if "date_purchased" not in asset:
1088        if required:
1089            raise ccs_asset_error("Date Purchased not specified!")
1090    else:
1091        mpat = re.compile("\d{4}-\d{1,2}-\d{1,2}")
1092        m = mpat.match(asset["date_purchased"])
1093        if m is None:
1094            raise ccs_asset_error("Invalid date format!")
1095    if "currency" in asset:
1096        if len(asset["currency"])!=3:
1097            raise ccs_asset_error("Invalid currency parameter!")
1098    if "price" in asset:
1099        try:
1100            v = float(asset["price"])
1101        except:
1102            (et, value, tb) = sys.exc_info()
1103            raise ccs_asset_error("Invalid price value! - %s" % value)
1104    if "supplier" in asset:
1105        if len(asset["supplier"]) > 64:
1106            raise ccs_asset_error("Supplier must be less than 64 characters")
1107    if "enabled" in asset:
1108        if asset["enabled"] != "t" and asset["enabled"] != "f":
1109            raise ccs_asset_error("Invalid enabled value!")
1110
1111    return
1112
1113#####################################################################
1114# Asset Types
1115#####################################################################
1116@exportViaXMLRPC(SESSION_RO, AUTH_ASSET_MANAGER)
1117def getTypes(session_id):
1118    """Returns a list of asset types"""
1119    session = getSessionE(session_id)
1120   
1121    sql = "SELECT at.asset_type_id, at.description, " \
1122            "coalesce(na.no_assets,0) AS no_assets, " \
1123            "coalesce(nsat.no_subasset_types, 0) AS no_subasset_types, " \
1124            "coalesce(nh.usage_count, 0) AS host_count " \
1125            "FROM asset_type at LEFT JOIN  n_subasset_types_by_asset_type " \
1126            "nsat ON at.asset_type_id=nsat.asset_type_id LEFT JOIN " \
1127            "n_assets_by_asset_type na ON at.asset_type_id=na.asset_type_id " \
1128            "LEFT JOIN n_hosts_by_asset_type nh ON " \
1129            "at.asset_type_id=nh.asset_type_id"
1130    res = session.query(sql, ())
1131    return res
1132
1133def getAssetTypeTemplateVariables(session_id):
1134    """Returns a list of asset types for use in templates"""
1135    session = getSessionE(session_id)
1136
1137    types = {}
1138    for type in getTypes(session_id):
1139        type["mangled_description"] = re.sub("[ \.\-]", "_", type["description"])
1140        if type["host_count"] > 0:
1141            sql = "SELECT DISTINCT h.host_name FROM host h, asset a, " \
1142                    "asset_location al, asset_type at WHERE " \
1143                    "a.asset_id=al.asset_id AND (h.asset_id=a.asset_id OR " \
1144                    "h.asset_id=al.attached_to) AND " \
1145                    "a.asset_type_id=at.asset_type_id AND at.asset_type_id=%s"
1146            res = session.query(sql, (type["asset_type_id"]))
1147            type["hosts"] = [ t["host_name"] for t in res ]
1148        else:
1149            type["hosts"] = []
1150        types[type["asset_type_id"]] = type
1151    return types
1152   
1153@exportViaXMLRPC(SESSION_RO, AUTH_ASSET_MANAGER)
1154def getNoSubassetTypes(session_id, asset_type_id):
1155    """Return the number of subasset types linked to the specified type"""
1156    session = getSessionE(session_id)
1157
1158    return session.getCountOf("SELECT count(*) FROM asset_type_subasset " \
1159            "WHERE asset_type_id=%s", (asset_type_id))
1160
1161@exportViaXMLRPC(SESSION_RO, AUTH_ASSET_MANAGER)
1162def getNoAssetsOfType(session_id, asset_type_id):
1163    """Return the number of assets linked to the specified type"""
1164    session = getSessionE(session_id)
1165
1166    return session.getCountOf("SELECT count(*) FROM asset " \
1167            "WHERE asset_type_id=%s", (asset_type_id))
1168   
1169@exportViaXMLRPC(SESSION_RO, AUTH_ASSET_MANAGER)
1170def getAssetType(session_id, asset_type_id):
1171    """Returns the description of the specified asset type"""
1172    session = getSessionE(session_id)
1173
1174    sql = "SELECT * FROM asset_type WHERE asset_type_id=%s"
1175    res = session.query(sql, (asset_type_id))
1176
1177    if len(res) != 1:
1178        return ""
1179
1180    return res[0]["description"]
1181
1182@registerEvent("assetTypeAdded")
1183@exportViaXMLRPC(SESSION_RW, AUTH_ASSET_MANAGER)
1184def addAssetType(session_id, asset_type_id, description):
1185    """Adds a new asset type to the database. The asset_type_id parameter is
1186    ignored and is simply present to allow the add/edit functions to have
1187    the same prototype.
1188    """
1189    session = getSessionE(session_id)
1190   
1191    sql = "INSERT INTO asset_type (asset_type_id, description) VALUES " \
1192            "(DEFAULT, %s)"
1193    session.execute(sql, (description))
1194
1195    # Retrieve the allocated ID
1196    sql = "SELECT currval('asset_type_asset_type_id_seq') as asset_type_id"
1197    res = session.query(sql, ())
1198   
1199    # Raise the event
1200    triggerEvent(session_id, "assetTypeAdded", \
1201            asset_type_id=res[0]["asset_type_id"])
1202   
1203    return res[0]["asset_type_id"]
1204
1205@registerEvent("assetTypeModified")
1206@exportViaXMLRPC(SESSION_RW, AUTH_ASSET_MANAGER)
1207def editAssetType(session_id, asset_type_id, description):
1208    """Updates the asset type"""
1209    session = getSessionE(session_id)
1210
1211    sql = "UPDATE asset_type SET description=%s WHERE asset_type_id=%s"
1212    res = session.execute(sql, (description, asset_type_id))
1213   
1214    # Raise the event
1215    triggerEvent(session_id, "assetTypeModified", asset_type_id=asset_type_id)
1216   
1217    return asset_type_id
1218
1219@registerEvent("assetTypeRemoved")
1220@exportViaXMLRPC(SESSION_RW, AUTH_ASSET_MANAGER)
1221def delAssetType(session_id, asset_type_id):
1222    """Removes the asset type"""
1223    session = getSessionE(session_id)
1224   
1225    # Raise the event
1226    triggerEvent(session_id, "assetTypeRemoved", asset_type_id=asset_type_id)
1227
1228    sql = "DELETE FROM asset_type WHERE asset_type_id=%s"
1229    session.execute(sql, (asset_type_id))
1230   
1231    return 0
1232
1233def _isValidAssetType(session_id, asset_type_id):
1234    session = getSessionE(session_id)
1235
1236    res = session.getCountOf("SELECT count(*) FROM asset_type " \
1237            "WHERE asset_type_id=%s", (asset_type_id))
1238    if res == 1:
1239        return True
1240
1241    raise ccs_asset_error("Invalid Asset Type ID: Not Found")
1242
1243#####################################################################
1244# Subasset Type Functions
1245#####################################################################
1246
1247@exportViaXMLRPC(SESSION_RO, AUTH_ASSET_MANAGER)
1248def getSubassetTypes(session_id):
1249    """Returns a list of Subasset types"""
1250    session = getSessionE(session_id)
1251   
1252    sql = "SELECT * FROM subasset_type"
1253    res = session.query(sql, ())
1254
1255    if len(res) < 1:
1256        return {}
1257
1258    # Grab some extra info
1259    sats= {}
1260    for at in res:
1261        satid = at["subasset_type_id"]
1262        data = {}
1263        data["subasset_type_id"] = satid
1264        data["description"] = at["description"]
1265        data["no_asset_types"] = getNoAssetTypes(session_id, satid)
1266        data["no_properties"] = getNoProperties(session_id, satid)
1267        sats["%s" % satid] = data
1268   
1269    return sats
1270
1271@exportViaXMLRPC(SESSION_RO, AUTH_ASSET_MANAGER)
1272def getNoAssetTypes(session_id, subasset_type_id):
1273    """Return the number of asset types that use the specified subasset type"""
1274    session = getSessionE(session_id)
1275
1276    return session.getCountOf("SELECT count(*) FROM asset_type_subasset " \
1277            "WHERE subasset_type_id=%s", (subasset_type_id))
1278
1279@exportViaXMLRPC(SESSION_RO, AUTH_ASSET_MANAGER)
1280def getNoProperties(session_id, subasset_type_id):
1281    """Return the number of properties linked to the specified subasset type"""
1282    session = getSessionE(session_id)
1283
1284    return session.getCountOf("SELECT count(*) FROM subasset_property " \
1285            "WHERE subasset_type_id=%s", (subasset_type_id))
1286   
1287@exportViaXMLRPC(SESSION_RO, AUTH_ASSET_MANAGER)
1288def getNoSubassetsOfType(session_id, subasset_type_id):
1289    """Return the number of subassets linked to the specified type"""
1290    session = getSessionE(session_id)
1291
1292    return session.getCountOf("SELECT count(*) FROM subasset sa, " \
1293            "asset_type_subasset ats WHERE sa.asset_type_subasset_id=" \
1294            "ats.asset_type_subasset_id AND ats.subasset_type_id=%s", \
1295            (subasset_type_id))
1296   
1297@exportViaXMLRPC(SESSION_RO, AUTH_ASSET_MANAGER)
1298def getSubassetType(session_id, subasset_type_id):
1299    """Returns the description of the specified subasset type"""
1300    session = getSessionE(session_id)
1301
1302    sql = "SELECT * FROM subasset_type WHERE subasset_type_id=%s"
1303    res = session.query(sql, (subasset_type_id))
1304
1305    if len(res) != 1:
1306        return ""
1307
1308    return res[0]["description"]
1309
1310@registerEvent("subassetTypeAdded")
1311@exportViaXMLRPC(SESSION_RW, AUTH_ASSET_MANAGER)
1312def addSubassetType(session_id, subasset_type_id, description):
1313    """Adds a new subasset type to the database. The subasset_type_id
1314    parameter is ignored and is simply present to allow the add/edit
1315    functions to have the same prototype.
1316    """
1317    session = getSessionE(session_id)
1318
1319    sql = "INSERT INTO subasset_type (subasset_type_id, description) " \
1320            "VALUES (DEFAULT, %s)"
1321    session.execute(sql, (description))
1322
1323    # Retrieve the allocated ID
1324    sql = "SELECT currval('subasset_type_subasset_type_id_seq') as " \
1325            "subasset_type_id"
1326    res = session.query(sql, ())
1327               
1328    # Raise the event
1329    triggerEvent(session_id, "subassetTypeAdded", \
1330            subasset_type_id=res[0]["subasset_type_id"])
1331   
1332    return res[0]["subasset_type_id"]
1333
1334@registerEvent("subassetTypeModified")
1335@exportViaXMLRPC(SESSION_RW, AUTH_ASSET_MANAGER)
1336def editSubassetType(session_id, subasset_type_id, description):
1337    """Updates the subasset type"""
1338    session = getSessionE(session_id)
1339   
1340    sql = "UPDATE subasset_type SET description=%s WHERE subasset_type_id=%s"
1341    session.execute(sql, (description, subasset_type_id))
1342   
1343    # Raise the event
1344    triggerEvent(session_id, "subassetTypeModified", \
1345            subasset_type_id=subasset_type_id)
1346   
1347    return subasset_type_id
1348
1349@registerEvent("subassetTypeRemoved")
1350@exportViaXMLRPC(SESSION_RW, AUTH_ASSET_MANAGER)
1351def delSubassetType(session_id, subasset_type_id):
1352    """Removes the subasset type"""
1353    session = getSessionE(session_id)
1354   
1355    # Raise the event
1356    triggerEvent(session_id, "subassetTypeRemoved", \
1357            subasset_type_id=subasset_type_id)
1358
1359    sql = "DELETE FROM subasset_type WHERE subasset_type_id=%s"
1360    res = session.execute(sql, (subasset_type_id))
1361   
1362    return 0
1363
1364def _isValidSubassetType(session_id, subasset_type_id):
1365    session = getSessionE(session_id)
1366
1367    res = session.getCountOf("SELECT count(*) FROM subasset_type " \
1368            "WHERE subasset_type_id=%s", (subasset_type_id))
1369    if res == 1:
1370        return
1371
1372    raise ccs_asset_error("Invalid Subasset Type ID: Not Found")
1373
1374#####################################################################
1375# Asset / Subasset Type Relationship
1376#####################################################################
1377
1378@registerEvent("subassetLinkAdded")
1379@exportViaXMLRPC(SESSION_RW, AUTH_ASSET_MANAGER)
1380def addSubassetLink(session_id, asset_type_id, subasset_type_id, name, required):
1381    """Adds a subasset type to the specified asset type"""
1382    session = getSessionE(session_id)
1383
1384    # Validate parameters
1385    _isValidAssetType(session_id, asset_type_id)
1386    _isValidSubassetType(session_id, subasset_type_id)
1387    if int(required) != 1 and int(required) !=0:
1388        return ccs_asset_error("Required must be 1 or 0!")
1389    if getNoAssetsOfType(session_id, asset_type_id)>0 and int(required)==1:
1390        return ccs_asset_error("Cannot add required " \
1391                "subasset types to an asset type with child assets!")
1392   
1393    if int(required) == 1:
1394        reqstr = "t"
1395    else:
1396        reqstr = "f"
1397   
1398    sql = "INSERT INTO asset_type_subasset (asset_type_id, " \
1399            "subasset_type_id, name, required) VALUES (%s, %s, %s, %s)"
1400    session.execute(sql, (asset_type_id, subasset_type_id, name, reqstr))
1401   
1402    # Retrieve the allocated ID
1403    sql = "SELECT currval('asset_type_subasset_asset_type_subasset_id_seq') " \
1404            "as asset_type_subasset_id"
1405    res = session.query(sql, ())
1406               
1407    # Raise the event
1408    triggerEvent(session_id, "subassetLinkAdded", \
1409            asset_type_subasset_id=res[0]["asset_type_subasset_id"])
1410   
1411    return res[0]["asset_type_subasset_id"]
1412
1413@registerEvent("subassetLinkRemoved")
1414@exportViaXMLRPC(SESSION_RW, AUTH_ASSET_MANAGER)
1415def removeSubassetLink(session_id, asset_type_subasset_id):
1416    """Removes a subasset type from the specified asset type"""
1417    session = getSessionE(session_id)
1418   
1419    # Raise the event
1420    triggerEvent(session_id, "subassetLinkRemoved", \
1421            asset_type_subasset_id=asset_type_subasset_id)
1422
1423    sql = "DELETE FROM asset_type_subasset WHERE asset_type_subasset_id=%s"
1424    session.execute(sql, (asset_type_subasset_id))
1425   
1426    return asset_type_subasset_id
1427
1428@registerEvent("subassetLinkModified")
1429@exportViaXMLRPC(SESSION_RW, AUTH_ASSET_MANAGER)
1430def updateSubassetLink(session_id, asset_type_subasset_id, required):
1431    """Update  a subasset type from the specified asset type"""
1432    session = getSessionE(session_id)
1433
1434    if int(required) != 1 and int(required) !=0:
1435        return ccs_asset_error("Required must be 1 or 0!")
1436
1437    # Check for linked assets
1438    if int(required)==1:
1439        sql = "SELECT asset_type_id FROM asset_type_subasset WHERE " \
1440                "asset_type_subasset_id=%s"
1441        res = session.query(sql, (asset_type_subasset_id))
1442        asset_type_id = res[0]["asset_type_id"]
1443        if getNoAssetsOfType(session_id, asset_type_id)>0:
1444            return ccs_asset_error("Cannot make subasset type required on " \
1445                    "an asset type with child assets!")
1446   
1447    # Update the status
1448    if int(required) == 1:
1449        reqstr = "t"
1450    else:
1451        reqstr = "f"
1452    sql = "UPDATE asset_type_subasset SET required=%s WHERE " \
1453            "asset_type_subasset_id=%s"
1454    session.execute(sql, (reqstr, asset_type_subasset_id))
1455   
1456    # Raise the event
1457    triggerEvent(session_id, "subassetLinkModified", \
1458            asset_type_subasset_id=asset_type_subasset_id)
1459   
1460    return asset_type_subasset_id
1461
1462@exportViaXMLRPC(SESSION_RW, AUTH_ASSET_MANAGER)
1463def getLinkedSubassets(session_id, asset_type_id):
1464    """Returns a list of subasset types that are linked to this asset type"""
1465    session = getSessionE(session_id)
1466   
1467    sql = "SELECT ats.asset_type_subasset_id, sat.subasset_type_id, " \
1468            "sat.description, ats.name, ats.required FROM subasset_type " \
1469            "sat, asset_type_subasset ats WHERE " \
1470            "sat.subasset_type_id=ats.subasset_type_id AND " \
1471            "ats.asset_type_id=%s ORDER BY name"
1472    res = session.query(sql, (asset_type_id))
1473
1474    return res
1475
1476def _validateAssetSubasset(session_id, asset):
1477    """Checks that the required subassets / properties are present"""
1478
1479    # Get the schema
1480    schema = getAssetSchema(session_id, asset["asset_type"])
1481
1482    # Check for invalid subasset types
1483    for asset_type_subasset_id,subasset in asset["subassets"].items():
1484        if int(asset_type_subasset_id) not in schema.keys():
1485            raise ccs_asset_error("Invalid subasset (%s) not in schema!" % \
1486                    asset_type_subasset_id)
1487        # Check subasset parameters
1488        if "enabled" in subasset:
1489            if subasset["enabled"] != "t" and subasset["enabled"] != "f":
1490                raise ccs_asset_error("Invalid enabled status for " \
1491                        "subasset (%s)" % asset_type_subasset_id)
1492        # Check the properties of this subasset
1493        _validateSubassetProperties(subasset["properties"], \
1494                schema[int(asset_type_subasset_id)])
1495
1496    # Check required subassets are present
1497    for asset_type_subasset_id,subasset in schema.items():
1498        if subasset["required"] != "t":
1499            continue
1500        if str(asset_type_subasset_id) not in asset["subassets"].keys():
1501            raise ccs_asset_error("Required subasset (%s - %s) not " \
1502                    "present!" % (subasset["description"], subasset["name"]))
1503
1504    return
1505   
1506def _isValidAssetTypeSubassetId(session_id, asset_type_subasset_id):
1507    """Checks the specified asset_type_subasset_id is OK"""
1508    session = getSessionE(session_id)
1509
1510    res = session.getCountOf("SELECT count(*) FROM asset_type_subasset " \
1511            "WHERE asset_type_subasset_id=%s", (asset_type_subasset_id))
1512    if res == 1:
1513        return
1514
1515    raise ccs_asset_error("Invalid Asset Type Subasset ID: Not Found")
1516
1517#####################################################################
1518# Asset Properties
1519#####################################################################
1520
1521@exportViaXMLRPC(SESSION_RO, AUTH_ASSET_MANAGER)
1522def getProperties(session_id):
1523    """Returns a list of asset properties"""
1524    session = getSessionE(session_id)
1525   
1526    sql = "SELECT * FROM asset_property"
1527    res = session.query(sql, ())
1528
1529    if len(res) < 1:
1530        return {}
1531
1532    # Grab some extra info
1533    aps = {}
1534    for ap in res:
1535        apid = ap["asset_property_id"]
1536        data = {}
1537        data["asset_property_id"] = apid
1538        data["description"] = ap["description"]
1539        data["no_subasset_properties"] = getNoSubassetProperties(session_id, \
1540                apid)
1541        aps["%s" % apid] = data
1542   
1543    return aps
1544
1545@exportViaXMLRPC(SESSION_RO, AUTH_ASSET_MANAGER)
1546def getNoSubassetProperties(session_id, asset_property_id):
1547    """Return the number of subasset types that use this property"""
1548    session = getSessionE(session_id)
1549
1550    return session.getCountOf("SELECT count(*) FROM subasset_property " \
1551            "WHERE asset_property_id=%s", (asset_property_id))
1552
1553@exportViaXMLRPC(SESSION_RO, AUTH_ASSET_MANAGER)
1554def getAssetProperty(session_id, asset_property_id):
1555    """Returns the description of the specified asset property"""
1556    session = getSessionE(session_id)
1557
1558    sql = "SELECT * FROM asset_property WHERE asset_property_id=%s"
1559    res = session.query(sql, (asset_property_id))
1560
1561    if len(res) != 1:
1562        return ""
1563
1564    return res[0]["description"]
1565
1566@registerEvent("assetPropertyAdded")
1567@exportViaXMLRPC(SESSION_RW, AUTH_ASSET_MANAGER)
1568def addAssetProperty(session_id, asset_property_id, description):
1569    """Adds a new asset property to the database. The asset_property_id
1570    parameter is ignored and is simply present to allow the add/edit
1571    functions to have the same prototype.
1572    """
1573    session = getSessionE(session_id)
1574
1575    sql = "INSERT INTO asset_property (asset_property_id, description) " \
1576            "VALUES (DEFAULT, %s)"
1577    session.execute(sql, (description))
1578
1579    # Retrieve the allocated ID
1580    sql = "SELECT currval('asset_property_asset_property_id_seq') as " \
1581            "asset_property_id"
1582    res = session.query(sql, ())
1583               
1584    # Raise the event
1585    triggerEvent(session_id, "assetPropertyAdded", \
1586            asset_property_id=res[0]["asset_property_id"])
1587   
1588    return asset_property_id
1589
1590@registerEvent("assetPropertyModified")
1591@exportViaXMLRPC(SESSION_RW, AUTH_ASSET_MANAGER)
1592def editAssetProperty(session_id, asset_property_id, description):
1593    """Updates the asset property"""
1594    session = getSessionE(session_id)
1595   
1596    sql = "UPDATE asset_property SET description=%s WHERE asset_property_id=%s"
1597    session.execute(sql, (description, asset_property_id))
1598   
1599    # Raise the event
1600    triggerEvent(session_id, "assetPropertyModified", \
1601            asset_property_id=asset_property_id)
1602   
1603    return asset_property_id
1604
1605@registerEvent("assetPropertyRemoved")
1606@exportViaXMLRPC(SESSION_RW, AUTH_ASSET_MANAGER)
1607def delAssetProperty(session_id, asset_property_id):
1608    """Removes the asset property"""
1609    session = getSessionE(session_id)
1610   
1611    # Raise the event
1612    triggerEvent(session_id, "assetPropertyRemoved", \
1613            asset_property_id=asset_property_id)
1614
1615    sql = "DELETE FROM asset_property WHERE asset_property_id=%s"
1616    session.execute(sql, (asset_property_id))
1617   
1618    return 0
1619
1620def _isValidAssetProperty(session_id, asset_property_id):
1621    session = getSessionE(session_id)
1622   
1623    res = session.getCountOf("SELECT count(*) FROM asset_property " \
1624            "WHERE asset_property_id=%s", (asset_property_id))
1625    if res == 1:
1626        return ""
1627
1628    raise ccs_asset_error("Invalid Asset Type ID: Not Found")
1629
1630def _validateSubassetProperties(properties, schema):
1631   
1632    # Check for invalid properties
1633    for subasset_property_id,value in properties.items():
1634        if int(subasset_property_id) not in schema["properties"].keys():
1635            raise ccs_asset_error("Invalid property (%s) not in schema!" % \
1636                    subasset_property_id)
1637        if len(str(value)) > 64:
1638            raise ccs_asset_error("Property value must be less than 64 " \
1639                    "characters!")
1640       
1641    # Check the required properties are present
1642    for subasset_property_id,prop in schema["properties"].items():
1643        if prop["required"] != "t":
1644            continue
1645        if str(subasset_property_id) not in properties.keys():
1646            raise ccs_asset_error("Required property (%s) not present!" % \
1647                    (prop["description"]))
1648   
1649    return
1650
1651def _isSubassetPropertyRequired(session_id, subasset_property_id):
1652    """Checks if the specified subasset_property is required"""
1653    session = getSessionE(session_id)
1654
1655    return session.getCountOf("SELECT count(*) FROM subasset_property " \
1656            "WHERE subasset_property_id=%s AND required='t'", \
1657            (subasset_property_id))
1658
1659#####################################################################
1660# Asset Property / Subasset Links
1661#####################################################################
1662
1663@registerEvent("subassetPropertyAdded")
1664@exportViaXMLRPC(SESSION_RW, AUTH_ASSET_MANAGER)
1665def addSubassetProperty(session_id, subasset_type_id, asset_property_id, \
1666        default_value, required):
1667    """Adds a property to the specified subasset type"""
1668    session = getSessionE(session_id)
1669
1670    # Validate parameters
1671    required = int(required)
1672    _isValidSubassetType(session_id, subasset_type_id)
1673    _isValidAssetProperty(session_id, asset_property_id)
1674    if required != 1 and required !=0:
1675        raise ccs_asset_error("Required must be 1 or 0!")
1676    if getNoSubassetsOfType(session_id, subasset_type_id)>0 and required:
1677        raise ccs_asset_error("Cannot add required property to a " \
1678                "subasset type with child assets!")
1679   
1680    if required:
1681        reqstr = "t"
1682    else:
1683        reqstr = "f"
1684   
1685    sql = "INSERT INTO subasset_property (subasset_type_id, " \
1686            "asset_property_id, default_value, required) " \
1687            "VALUES (%s, %s, %s, %s)"
1688    session.execute( sql, (subasset_type_id, asset_property_id, \
1689            default_value, reqstr))
1690   
1691    # Retrieve the allocated ID
1692    sql = "SELECT currval('subasset_property_subasset_property_id_seq') " \
1693            "as subasset_property_id"
1694    res = session.query(sql, ())
1695               
1696    # Raise the event
1697    triggerEvent(session_id, "subassetPropertyAdded", \
1698            subasset_property_id=res[0]["subasset_property_id"])
1699   
1700    return res[0]["subasset_property_id"]
1701
1702@registerEvent("subassetPropertyModified")
1703@exportViaXMLRPC(SESSION_RW, AUTH_ASSET_MANAGER)
1704def updateSubassetProperty(session_id, subasset_property_id, default_value, \
1705        required):
1706    """Update a subasset properties details"""
1707    session = getSessionE(session_id)
1708
1709    if int(required) != 1 and int(required) !=0:
1710        raise ccs_asset_error("Required must be 1 or 0!")
1711    if subasset_property_id == "":
1712        raise ccs_asset_error("Missing subasset property id!")
1713
1714    # Check for linked subassets
1715    if int(required)==1:
1716        sql = "SELECT subasset_type_id FROM subasset_property WHERE " \
1717                "subasset_property_id=%s"
1718        res = session.query(sql, (subasset_property_id))
1719        subasset_type_id = res[0]["subasset_type_id"]
1720        if getNoSubassetsOfType(session_id, subasset_type_id)>0:
1721            raise ccs_asset_error("Cannot make property required on an " \
1722                    "subasset type with child assets!")
1723   
1724    # Update the status
1725    if int(required) == 1:
1726        reqstr = "t"
1727    else:
1728        reqstr = "f"
1729    sql = "UPDATE subasset_property SET required=%s, default_value=%s WHERE " \
1730            "subasset_property_id=%s"
1731    session.execute(sql, (reqstr, default_value, subasset_property_id))
1732   
1733    # Raise the event
1734    triggerEvent(session_id, "subassetPropertyModified", \
1735            subasset_property_id=subasset_property_id)
1736   
1737    return subasset_property_id
1738
1739@exportViaXMLRPC(SESSION_RO, AUTH_ASSET_MANAGER)
1740def getNoDataOfSubassetProperty(session_id, subasset_property_id):
1741    """Return the number of peices of data for this property in the database"""
1742    session = getSessionE(session_id)
1743
1744    return session.getCountOf("SELECT * FROM asset_data ad, " \
1745            "subasset_property sp WHERE ad.subasset_property_id=" \
1746            "sp.subasset_property_id AND sp.subasset_property_id=%s", \
1747            (subasset_property_id))
1748   
1749@registerEvent("subassetPropertyRemoved")
1750@exportViaXMLRPC(SESSION_RW, AUTH_ASSET_MANAGER)
1751def removeSubassetProperty(session_id, subasset_property_id):
1752    """Removes a property from the specified subasset type"""
1753    session = getSessionE(session_id)
1754
1755    if getNoDataOfSubassetProperty(session_id, subasset_property_id)>0:
1756        raise ccs_asset_error("Cannot remove subasset property which has " \
1757                "asset data attached!")
1758       
1759    # Raise the event
1760    triggerEvent(session_id, "subassetPropertyRemoved", \
1761            subasset_property_id=subasset_property_id)
1762           
1763    sql = "DELETE FROM subasset_property WHERE subasset_property_id=%s"
1764    session.execute(sql, (subasset_property_id))
1765   
1766    return 0
1767
1768@exportViaXMLRPC(SESSION_RO, AUTH_ASSET_MANAGER)
1769def getLinkedProperties(session_id, subasset_type_id):
1770    """Returns a list of properties that are linked to this subasset type"""
1771    session = getSessionE(session_id)
1772
1773    if subasset_type_id == "":
1774        raise ccs_asset_error("Missing subasset type id!")
1775   
1776    sql = "SELECT sap.subasset_property_id, sap.subasset_type_id, " \
1777            "sap.asset_property_id, sap.required, sap.default_value, " \
1778            "ap.description FROM subasset_property sap, asset_property ap " \
1779            "WHERE sap.asset_property_id=ap.asset_property_id AND " \
1780            "sap.subasset_type_id=%s ORDER BY ap.description"
1781    res = session.query(sql, (subasset_type_id))
1782
1783    return res
1784
1785def _isValidSubassetPropertyId(session_id, subasset_property_id):
1786    """Checks the specified subasset_property_id is OK"""
1787    session = getSessionE(session_id)
1788
1789    res = session.getCountOf("SELECT count(*) FROM subasset_property " \
1790            "WHERE subasset_property_id=%s", (subasset_property_id))
1791    if res == 1:
1792        return
1793
1794    raise ccs_asset_error("Invalid Subasset Property ID: Not Found")
1795
1796
1797#####################################################################
1798# Subassets
1799#####################################################################
1800class ccs_subasset(ccs_class):
1801    """Provides an abstract interface to a subasset in the CRCnet Configuration
1802    System.
1803   
1804    The members of this class loosely map to the fields of the subasset table
1805    in the database.
1806    """
1807   
1808    def __init__(self, session_id, subasset_id):
1809        """Initialises a new class for a specified subasset.
1810
1811        Parent must be a reference to the core crcnetd class.
1812       
1813        The specified session must be valid and have appropriate access to
1814        the database for the tasks you intend to perform with the class. All
1815        database access / configuration manipulation triggered by this
1816        instance will pass through the specified session.
1817        """
1818
1819        self._errMsg = ""
1820        self._commit = 0
1821        self._csInit = ""
1822       
1823        session = getSession(session_id)
1824        if session is None:
1825            raise ccs_asset_error("Invalid session id")
1826        self._session_id = session_id
1827       
1828        # See if the specified subasset id makes sense
1829        sql = "SELECT * FROM subasset WHERE subasset_id=%s"
1830        res = session.query(sql, (subasset_id))
1831        if len(res) < 1:
1832            # DB Query failed
1833            raise ccs_asset_error("Invalid subasset. Unable to retrieve " 
1834                "details")
1835       
1836        # Store details
1837        self.subasset_id = subasset_id
1838        self._properties = res[0]
1839
1840
1841    def moveToSite(self, site_id):
1842        """Updates the subasset's location to the specified site"""
1843        session = getSessionE(self._session_id)
1844       
1845        asset = ccs_asset(session.session_id, self["asset_id"])
1846        return asset.moveToSite(site_id)
1847   
1848    def attachTo(self, asset_id):
1849        """Attaches this subasset to the specified asset"""
1850        session = getSessionE(self._session_id)
1851       
1852        # No-op if someone tries to attach us to ourself...
1853        print self["asset_id"], asset_id
1854        if self["asset_id"] == asset_id:
1855            return True
1856
1857        asset = ccs_asset(session.session_id, self["asset_id"])
1858        return asset.attachTo(asset_id)
1859       
1860    def moveToStock(self):
1861        """Updates the subasset's location to the default stock site"""
1862        session = getSessionE(self._session_id) 
1863       
1864        asset = ccs_asset(session.session_id, self["asset_id"])
1865        return asset.moveToStock()
1866   
1867    def supportsFunction(self, function):
1868        """Returns true if this subasset supports the specified function"""
1869        session = getSessionE(self._session_id)
1870       
1871        return session.getCountOf("SELECT count(*) FROM subasset sa, " \
1872            "asset_type_subasset ats, subasset_type_map stm WHERE " \
1873            "sa.asset_type_subasset_id=ats.asset_type_subasset_id AND " \
1874            "ats.subasset_type_id=stm.subasset_type_id AND " \
1875            "stm.asset_function=%s AND sa.subasset_id=%s", \
1876            (function, self.subasset_id))
1877
1878    @exportViaXMLRPC(SESSION_RO, AUTH_ASSET_MANAGER, True)
1879    def getSubassetFunctions(self):
1880        session = getSessionE(self._session_id)
1881       
1882        funcs = []
1883        res = session.query("SELECT stm.asset_function FROM " \
1884                "subasset_type_map stm, asset_type_subasset ats, " \
1885                "subasset s WHERE stm.subasset_type_id=" \
1886                "ats.subasset_type_id AND ats.asset_type_subasset_id=" \
1887                "s.asset_type_subasset_id AND s.subasset_id=%s", \
1888                (self.subasset_id))
1889        for row in res:
1890            funcs.append(row["asset_function"])
1891        return funcs
1892
1893    def attachedToHost(self, host_id):
1894        """Returns true if this subasset is attached to the specified host"""
1895        session = getSessionE(self._session_id)
1896   
1897        return session.getCountOf("SELECT * FROM interface_subassets isa, " \
1898                "asset_location al, host h WHERE isa.subasset_id=%s AND " \
1899                "isa.asset_id=al.asset_id AND al.attached_to=h.asset_id", \
1900                self.subasset_id)
1901       
1902    def availableForInterface(self, host_id):
1903        """Returns true if this asset can be an interface on the specified host
1904        """
1905        session = getSessionE(self._session_id)
1906       
1907        if self.attachedToHost(host_id):
1908            return True
1909 
1910        return session.getCountOf("SELECT COUNT(*) FROM  asset_location " \
1911                "al, interface_subassets isa LEFT JOIN host h ON " \
1912                "isa.asset_id=h.asset_id WHERE isa.asset_id=al.asset_id AND " \
1913                "((al.site_id IN (SELECT site_id FROM asset_stock_location) " \
1914                "OR al.site_id IN (SELECT site_id FROM host WHERE host_id=%s) " \
1915                ")OR h.host_id=%s) AND subasset_id=%s", \
1916                (host_id, host_id, self.subasset_id))
1917
1918    @registerEvent("subassetStateChanged")
1919    @exportViaXMLRPC(SESSION_RW, AUTH_ASSET_MANAGER, True)
1920    def updateSubassetEnabled(self, value):
1921        """Updates the enabled status of the subasset"""
1922        session = getSessionE(self._session_id)
1923       
1924        # Validate
1925        if value != "t" and value != "f":
1926            raise ccs_asset_error("Enabled must be t or f!")
1927       
1928        # If a changeset is not already active, start one for this batch
1929        self._forceChangeset("Updated status of subasset #%s" % \
1930                self.subasset_id, "subasset")
1931       
1932        asset_id = self._properties["asset_id"]
1933
1934        # Do the update
1935        sql = "UPDATE subasset SET enabled=%s WHERE subasset_id=%s"
1936        res = session.execute(sql, (value, self.subasset_id))
1937           
1938        # Record the change
1939        sql = "INSERT INTO asset_event (asset_id, event_type, username, " \
1940                "data1, data2) VALUES (%%s, %%s, %%s, '%s:enabled', %%s)" % \
1941                (self.subasset_id)
1942        p = (asset_id, ASSET_EVENT_PROPERTY_UPDATED, session.username, value)
1943        res = session.execute(sql, p)
1944       
1945        # Raise the event
1946        triggerEvent(session_id, "subassetStateChanged", \
1947                subasset_id=self.subasset_id)
1948                   
1949        return self.returnSuccess()
1950   
1951    @registerEvent("subassetPropertyUpdated")
1952    @exportViaXMLRPC(SESSION_RW, AUTH_ASSET_MANAGER, True)
1953    def updateSubassetPropertyData(self, subasset_property_id, value):
1954        """Updates an asset property in the database"""
1955        session = getSessionE(self._session_id)
1956       
1957        # Validate id
1958        _isValidSubassetPropertyId(self._session_id, subasset_property_id)
1959        # Validate data
1960        if len(value) > 64:
1961            raise ccs_asset_error("Property value must be less than 64 " \
1962                    "characters!")
1963        if _isSubassetPropertyRequired(self._session_id, \
1964                subasset_property_id) and len(value)==0:
1965            raise ccs_asset_error("Cannot set blank value on required " \
1966                    "property")
1967       
1968        # If a changeset is not already active, start one for this batch
1969        self._forceChangeset("Updated property of subasset #%s" % \
1970                self.subasset_id, "subasset")
1971           
1972        asset_id = self._properties["asset_id"]
1973       
1974        # Check if the data exists currently
1975        res = session.getCountOf("SELECT count(*) FROM asset_data " \
1976                "WHERE subasset_id=%s AND subasset_property_id=%s", \
1977                (self.subasset_id, subasset_property_id))
1978        if res == 1:
1979            # Do the update
1980            sql = "UPDATE asset_data SET value=%s WHERE subasset_id=%s AND " \
1981                    "subasset_property_id=%s"
1982            session.execute(sql, (value, self.subasset_id, \
1983                    subasset_property_id))
1984        else:
1985            # Do the insert
1986            sql = "INSERT INTO asset_data (subasset_id, subasset_property_id, " \
1987                    "value) VALUES (%s, %s, %s)"
1988            session.execute(sql, (self.subasset_id, subasset_property_id, \
1989                    value))
1990           
1991        # Record the change
1992        sql = "INSERT INTO asset_event (asset_id, event_type, username, " \
1993                "data1, data2) VALUES (%%s, %%s, %%s, '%s:%s', %%s)" % \
1994                (self.subasset_id, subasset_property_id)
1995        p = (asset_id, ASSET_EVENT_PROPERTY_UPDATED, session.username, value)
1996        session.execute(sql, p)
1997       
1998        # Raise the event
1999        triggerEvent(self._session_id, "subassetPropertyUpdated", \
2000                subasset_id=self.subasset_id, \
2001                subasset_property_id=subasset_property_id)
2002                   
2003        return self.returnSuccess()
2004   
2005    def getTemplateVariables(self):
2006       
2007        asset = ccs_asset(self._session_id, self["asset_id"])
2008        return asset.getTemplateVariables()
2009
2010@exportViaXMLRPC(SESSION_RO, AUTH_ASSET_MANAGER)
2011def getSubassetName(session_id, subasset_id):
2012    """Returns the name of the specified subasset"""
2013    session = getSessionE(session_id)
2014
2015    sql = "SELECT ats.name FROM subasset sa, asset_type_subasset ats WHERE " \
2016            "sa.asset_type_subasset_id=ats.asset_type_subasset_id AND " \
2017            "sa.subasset_id=%s"
2018    res = session.query(sql, (subasset_id))
2019    if len(res)!=1:
2020        raise ccs_asset_error("Invalid result returned in getSubassetName")
2021   
2022    return res[0]["name"]
2023
2024def _getSubassetPropertyDescription(session_id, subasset_property_id):
2025    """Returns the description of the specified subasset property"""
2026    session = getSessionE(session_id)
2027
2028    sql = "SELECT ap.description FROM subasset_property sap, " \
2029            "asset_property ap WHERE sap.asset_property_id=" \
2030            "ap.asset_property_id AND sap.subasset_property_id=%s"
2031    res = session.query(sql, (subasset_property_id))
2032    if len(res)!=1:
2033       raise ccs_asset_error("Invalid result returned in " \
2034               "getSubassetPropertyDescription")
2035   
2036    return res[0]["description"]
2037
2038def _isValidSubasset(session_id, subasset_id):
2039    """Checks the specified subasset_id is OK"""
2040    session = getSessionE(session_id)
2041
2042    res = session.getCountOf("SELECT count(*) FROM subasset " \
2043            "WHERE subasset_id=%s", (subasset_id))
2044    if res == 1:
2045        return
2046
2047    raise ccs_asset_error("Invalid Subsset ID: Not Found")
2048
2049#####################################################################
2050# Webserver Portion to resolve MAC addresses to Asset details
2051#####################################################################
2052class ccs_maclookup(resource.Resource):
2053    """Implements a simple MAC lookup resource
2054
2055    Called when the main server receives a query for /maclookup/*
2056    """
2057
2058    # Name for the resource
2059    resourceName = "MAC Lookup Server"
2060   
2061    # Mark as leaf so that render gets called
2062    isLeaf = 1
2063       
2064    def render(self, request):
2065        session = getSession(ADMIN_SESSION_ID)
2066       
2067        mac = os.path.basename(request.path)
2068       
2069        # Verify MAC address format
2070        if not isValidMAC(mac):
2071            request.setResponseCode(400, "Invalid MAC address format!")
2072            request.finish
2073            return server.NOT_DONE_YET
2074       
2075        log_debug("Received maclookup request for %s" % mac)
2076
2077        # Lookup mac
2078        sql = "SELECT h.host_name, h.ip_address, a.asset_id, am.mac FROM " \
2079                "asset_macs am, asset a LEFT JOIN host h ON a.asset_id=" \
2080                "h.asset_id WHERE am.asset_id=a.asset_id AND upper(am.mac)" \
2081                "=upper(%s)"
2082        try:
2083            res = session.query(sql, (mac.strip()))
2084        except:
2085            (type, value, tb) = sys.exc_info()
2086            request.setResponseCode(500, "Database query failed - %s" % \
2087                    value)
2088            request.finish()
2089            return server.NOT_DONE_YET
2090       
2091        if len(res)!=1:
2092            request.setResponseCode(404, "Specified MAC address not found!")
2093            request.finish()
2094            return server.NOT_DONE_YET
2095       
2096        host_name = res[0]["host_name"]
2097        ip_address = res[0]["ip_address"]
2098       
2099        # Check if this asset is attached to another asset that is a host
2100        if host_name == "":
2101            sql = "SELECT h.host_name, h.ip_address FROM asset_location al " \
2102                    "LEFT JOIN host h ON al.attached_to=h.asset_id WHERE " \
2103                    "al.asset_id=%s AND al.attached_to IS NOT NULL"
2104            try:
2105                res2 = session.query(sql, res[0]["asset_id"])
2106            except:
2107                (type, value, tb) = sys.exc_info()
2108                request.setResponseCode(500, "Database query failed - %s" % \
2109                        value)
2110                request.finish()
2111                return server.NOT_DONE_YET
2112           
2113            if len(res2)==1:
2114                host_name = res2[0]["host_name"]
2115                ip_address = res2[0]["ip_address"]
2116           
2117        # Write out info about the host
2118        rs = "FOUND=1\nASSET_ID=%s\nHOST_NAME=%s\nHOST_IP=%s\nMAC=%s\n" % \
2119                (res[0]["asset_id"], host_name, ip_address, res[0]["mac"])
2120        return rs
2121   
2122def ccs_init():
2123    # Register MAC Address Handler
2124    registerResource("maclookup", ccs_maclookup)
Note: See TracBrowser for help on using the repository browser.