|
1 #!/usr/bin/python2.6 |
|
2 # |
|
3 # CDDL HEADER START |
|
4 # |
|
5 # The contents of this file are subject to the terms of the |
|
6 # Common Development and Distribution License (the "License"). |
|
7 # You may not use this file except in compliance with the License. |
|
8 # |
|
9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
|
10 # or http://www.opensolaris.org/os/licensing. |
|
11 # See the License for the specific language governing permissions |
|
12 # and limitations under the License. |
|
13 # |
|
14 # When distributing Covered Code, include this CDDL HEADER in each |
|
15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
|
16 # If applicable, add the following below this CDDL HEADER, with the |
|
17 # fields enclosed by brackets "[]" replaced with your own identifying |
|
18 # information: Portions Copyright [yyyy] [name of copyright owner] |
|
19 # |
|
20 # CDDL HEADER END |
|
21 # |
|
22 # Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. |
|
23 |
|
24 """ |
|
25 AI set-criteria |
|
26 """ |
|
27 |
|
28 import gettext |
|
29 import os.path |
|
30 import sys |
|
31 import lxml.etree |
|
32 from optparse import OptionParser |
|
33 |
|
34 import publish_manifest as pub_man |
|
35 import osol_install.auto_install.AI_database as AIdb |
|
36 import osol_install.libaiscf as smf |
|
37 |
|
38 def parse_options(cmd_options=None): |
|
39 """ |
|
40 Parse and validate options |
|
41 Args: Optional cmd_options, used for unit testing. Otherwise, cmd line |
|
42 options handled by OptionParser |
|
43 Returns: the DataFiles object populated and initialized |
|
44 Raises: The DataFiles initialization of manifest(s) A/I, SC, SMF looks for |
|
45 many error conditions and, when caught, are flagged to the user |
|
46 via raising SystemExit exceptions. |
|
47 """ |
|
48 |
|
49 usage = _("usage: %prog -n service_name -m AI_manifest_name" |
|
50 " [-c|-a <criteria=value|range> ...] | -C criteria_file") |
|
51 |
|
52 parser = OptionParser(usage=usage, prog="set-criteria") |
|
53 parser.add_option("-a", dest="criteria_a", action="append", |
|
54 default=[], help=_("Specify criteria to append: " |
|
55 "<-a criteria=value|range> ...")) |
|
56 parser.add_option("-c", dest="criteria_c", action="append", |
|
57 default=[], help=_("Specify criteria: " |
|
58 "<-c criteria=value|range> ...")) |
|
59 parser.add_option("-C", dest="criteria_file", |
|
60 default=None, help=_("Specify name of criteria " |
|
61 "XML file.")) |
|
62 parser.add_option("-m", dest="manifest_name", |
|
63 default=None, help=_("Specify name of manifest " |
|
64 "to set criteria for.")) |
|
65 parser.add_option("-n", dest="service_name", |
|
66 default=None, help=_("Specify name of install " |
|
67 "service.")) |
|
68 |
|
69 # Get the parsed options using parse_args(). We know we don't have |
|
70 # args, so check to make sure there are none. |
|
71 options, args = parser.parse_args(cmd_options) |
|
72 if len(args): |
|
73 parser.error(_("Unexpected arguments: %s" % args)) |
|
74 |
|
75 # Check that we have the install service's name and |
|
76 # an AI manifest name |
|
77 if options.service_name is None or options.manifest_name is None: |
|
78 parser.error(_("Missing one or more required options.")) |
|
79 |
|
80 # check that we aren't mixing -a, -c, and -C |
|
81 if (options.criteria_a and options.criteria_c) or \ |
|
82 (options.criteria_a and options.criteria_file) or \ |
|
83 (options.criteria_c and options.criteria_file): |
|
84 parser.error(_("Options used are mutually exclusive.")) |
|
85 |
|
86 return options |
|
87 |
|
88 def check_published_manifest(service_dir, db, manifest_name): |
|
89 """ |
|
90 Used for checking that a manifest is already published in the |
|
91 install service specified. Checks to make sure manifest |
|
92 exists in the install service's DB, and that the manifest also |
|
93 exists in the install service's published files area. |
|
94 Args: |
|
95 service_dir - config directory of install service to check. |
|
96 db - db object of install service to check against. |
|
97 manifest_name - name of manifest to check. |
|
98 Postconditions: None |
|
99 Returns: True if manifest exists in install service |
|
100 False if manifest does not exist. |
|
101 """ |
|
102 |
|
103 # Check if manifest exists in the service's criteria DB. |
|
104 if AIdb.sanitizeSQL(manifest_name) not in AIdb.getManNames(db.getQueue()): |
|
105 print(_("Error: install service does not contain the specified " |
|
106 "manifest: %s") % manifest_name) |
|
107 return False |
|
108 |
|
109 # Check if manifest file exists in the service's published area. |
|
110 published_path = os.path.join(service_dir, "AI_data", manifest_name) |
|
111 |
|
112 if not os.path.exists(published_path): |
|
113 print(_("Error: manifest missing from published area: %s") % |
|
114 published_path) |
|
115 return False |
|
116 |
|
117 return True |
|
118 |
|
119 def format_value(crit, value): |
|
120 """ |
|
121 Format's a value (for use with set_criteria()) based on its |
|
122 criteria type. |
|
123 Args: crit - the criteria name. |
|
124 value - the value to format. |
|
125 Returns: |
|
126 Formatted value for (used by set_criteria()) to use in |
|
127 a string to query the install service's DB. |
|
128 """ |
|
129 # For the value "unbounded", we store this as "NULL" in the DB. |
|
130 if value == "unbounded": |
|
131 return "NULL" |
|
132 else: |
|
133 formatted_val = "'" + AIdb.sanitizeSQL(str(value).upper()) + "'" |
|
134 |
|
135 # If its the "mac" criteria, must add a special hex operand |
|
136 if crit == "mac": |
|
137 return "x" + formatted_val |
|
138 |
|
139 return formatted_val |
|
140 |
|
141 def set_criteria(criteria, manifest_name, db, append=False): |
|
142 """ |
|
143 Set a manifest's record in the criteria database with the |
|
144 criteria provided. |
|
145 If append is True -- append ones that aren't already set for |
|
146 the manifest, and replace ones that are. |
|
147 if append is False -- completely remove all criteria already |
|
148 set for the manifest, and use only the criteria specified. |
|
149 """ |
|
150 |
|
151 # Build a list of criteria nvpairs to update |
|
152 nvpairs = list() |
|
153 |
|
154 # we need to fill in the criteria or NULLs for each criteria the database |
|
155 # supports (so iterate over each criteria) |
|
156 for crit in AIdb.getCriteria(db.getQueue(), onlyUsed=False, strip=True): |
|
157 |
|
158 # Get the value from the manifest |
|
159 values = criteria[crit] |
|
160 |
|
161 # the critera manifest didn't specify this criteria |
|
162 if values is None: |
|
163 # If we not appending criteria, then we must write in NULLs |
|
164 # for this criteria since we're removing all criteria not |
|
165 # specified. |
|
166 if not append: |
|
167 # if the criteria we're processing is a range criteria, fill in |
|
168 # NULL for two columns, MINcrit and MAXcrit |
|
169 if AIdb.isRangeCriteria(db.getQueue(), crit): |
|
170 nvpairs.append("MIN" + crit + "=NULL") |
|
171 nvpairs.append("MAX" + crit + "=NULL") |
|
172 # this is a single value |
|
173 else: |
|
174 nvpairs.append(crit + "=NULL") |
|
175 |
|
176 # this is a single criteria (not a range) |
|
177 elif isinstance(values, basestring): |
|
178 # translate "unbounded" to a database NULL |
|
179 if values == "unbounded": |
|
180 nvstr = crit + "=NULL" |
|
181 else: |
|
182 # use lower case for text strings |
|
183 nvstr = crit + "='" + AIdb.sanitizeSQL(str(values).lower()) \ |
|
184 + "'" |
|
185 nvpairs.append(nvstr) |
|
186 |
|
187 # Else the values are a list this is a range criteria |
|
188 else: |
|
189 # Set the MIN column for this range criteria |
|
190 nvpairs.append("MIN" + crit + "=" + format_value(crit, values[0])) |
|
191 |
|
192 # Set the MAX column for this range criteria |
|
193 nvpairs.append("MAX" + crit + "=" + format_value(crit, values[1])) |
|
194 |
|
195 query = "UPDATE manifests SET " + ",".join(nvpairs) + \ |
|
196 " WHERE name='" + manifest_name + "'" |
|
197 |
|
198 # update the DB |
|
199 query = AIdb.DBrequest(query, commit=True) |
|
200 db.getQueue().put(query) |
|
201 query.waitAns() |
|
202 # in case there's an error call the response function (which |
|
203 # will print the error) |
|
204 query.getResponse() |
|
205 |
|
206 |
|
207 if __name__ == '__main__': |
|
208 gettext.install("ai", "/usr/lib/locale") |
|
209 |
|
210 options = parse_options() |
|
211 |
|
212 # Get the SMF service object for the install service specified. |
|
213 try: |
|
214 svc = smf.AIservice(smf.AISCF(FMRI="system/install/server"), |
|
215 options.service_name) |
|
216 except KeyError: |
|
217 raise SystemExit(_("Error: Failed to find service %s") % |
|
218 options.service_name) |
|
219 |
|
220 # Get the install service's data directory and database path |
|
221 try: |
|
222 port = svc['txt_record'].rsplit(':')[-1] |
|
223 except KeyError: |
|
224 raise SystemExit(_("SMF data for service %s is corrupt.\n") % |
|
225 options.service_name) |
|
226 service_dir = os.path.abspath("/var/ai/" + port) |
|
227 database = os.path.join(service_dir, "AI.db") |
|
228 |
|
229 # Check that the service directory and database exist |
|
230 if not (os.path.isdir(service_dir) and os.path.exists(database)): |
|
231 raise SystemExit("Error: Invalid AI service directory: %s" % |
|
232 service_dir) |
|
233 |
|
234 # Open the database |
|
235 db = AIdb.DB(database, commit=True) |
|
236 |
|
237 # Check to make sure that the manifest whose criteria we're |
|
238 # updating exists in the install service. |
|
239 if not check_published_manifest(service_dir, db, options.manifest_name): |
|
240 raise SystemExit(1) |
|
241 |
|
242 # Process and validate criteria from -a, -c, or -C, and store |
|
243 # store the criteria in a Criteria object. |
|
244 try: |
|
245 if options.criteria_file: |
|
246 root = pub_man.verifyCriteria(pub_man.DataFiles.criteriaSchema, |
|
247 options.criteria_file, db) |
|
248 elif options.criteria_a: |
|
249 criteria_dict = pub_man.criteria_to_dict(options.criteria_a) |
|
250 root = pub_man.verifyCriteriaDict(pub_man.DataFiles.criteriaSchema, |
|
251 criteria_dict, db) |
|
252 elif options.criteria_c: |
|
253 criteria_dict = pub_man.criteria_to_dict(options.criteria_c) |
|
254 root = pub_man.verifyCriteriaDict(pub_man.DataFiles.criteriaSchema, |
|
255 criteria_dict, db) |
|
256 except (AssertionError, IOError, ValueError) as err: |
|
257 raise SystemExit(err) |
|
258 except (lxml.etree.LxmlError) as err: |
|
259 raise SystemExit(_("Error:\tmanifest error: %s") % err) |
|
260 |
|
261 # Instantiate a Criteria object with the XML DOM of the criteria. |
|
262 criteria = pub_man.Criteria(root) |
|
263 |
|
264 # Ensure the criteria we're adding/setting for this manifest doesn't |
|
265 # cause a criteria collision in the DB. |
|
266 colliding_criteria = pub_man.find_colliding_criteria(criteria, db, |
|
267 exclude_manifests=[options.manifest_name]) |
|
268 # If we're appending criteria pass the manifest name |
|
269 if options.criteria_a: |
|
270 pub_man.find_colliding_manifests(criteria, db, colliding_criteria, |
|
271 append_manifest=options.manifest_name) |
|
272 else: |
|
273 pub_man.find_colliding_manifests(criteria, db, colliding_criteria, |
|
274 append_manifest=None) |
|
275 |
|
276 # Update the criteria for this manifest. |
|
277 if options.criteria_a: |
|
278 set_criteria(criteria, options.manifest_name, db, append=True) |
|
279 else: |
|
280 set_criteria(criteria, options.manifest_name, db, append=False) |
|
281 |