remove code related to labeled brands
[unleashed-pkg5.git] / src / modules / client / linkedimage / zone.py
blob529887112443a2e021ab43a6a9ad3cb432cce482
1 #!/usr/bin/python2.7
3 # CDDL HEADER START
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.
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.
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]
20 # CDDL HEADER END
24 # Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
27 """
28 Zone linked image module classes. Zone linked images only support
29 child attach. Zone linked image child configuration information is all
30 derived from zonecfg(1m) options and this plugin, no child configuration
31 information is stored within a parent image.
32 """
34 # standard python classes
35 import os
36 import tempfile
38 # pkg classes
39 import pkg.client.api_errors as apx
40 import pkg.client.pkgdefs as pkgdefs
41 import pkg.pkgsubprocess
43 from pkg.client.debugvalues import DebugValues
45 # import linked image common code
46 import common as li # Relative import; pylint: disable=W0403
48 # W0511 XXX / FIXME Comments; pylint: disable=W0511
49 # XXX: should be defined by libzonecfg python wrapper
50 # pylint: enable=W0511
52 ZONE_GLOBAL = "global"
54 ZONE_STATE_STR_CONFIGURED = "configured"
55 ZONE_STATE_STR_INCOMPLETE = "incomplete"
56 ZONE_STATE_STR_UNAVAILABLE = "unavailable"
57 ZONE_STATE_STR_INSTALLED = "installed"
58 ZONE_STATE_STR_READY = "ready"
59 ZONE_STATE_STR_MOUNTED = "mounted"
60 ZONE_STATE_STR_RUNNING = "running"
61 ZONE_STATE_STR_SHUTTING_DOWN = "shutting_down"
62 ZONE_STATE_STR_DOWN = "down"
64 zone_installed_states = [
65 ZONE_STATE_STR_INSTALLED,
66 ZONE_STATE_STR_READY,
67 ZONE_STATE_STR_MOUNTED,
68 ZONE_STATE_STR_RUNNING,
69 ZONE_STATE_STR_SHUTTING_DOWN,
70 ZONE_STATE_STR_DOWN
75 # If we're operating on a zone image it's very tempting to want to know
76 # the zone name of that image. Unfortunately, there's no good way to
77 # determine this, and more importantly, there is no real need to know
78 # the zone name. The only reason to know the name of a linked image is
79 # so that we can import linked image properties from the associated
80 # parent image. But for zones we should never do this. When operating
81 # on zone images we may not have access to the associated parent image
82 # (for example, when running inside a zone). So every zone image must
83 # contain all the information needed to do a pkg operation at the start
84 # of that operation. i.e., the linked image information required for
85 # operations must be pushed (or exported) from the parent image. We can
86 # not pull (or import) this information from the parent image (in the
87 # cases where the parent image is accessible).
90 # There are lots of possible execution modes that we can find ourselves
91 # in. Here are some of the possibilities:
93 # 1) in a gz operating on /
94 # 2) in a gz operating on a zone linked image via pkg -R
95 # 3) in a ngz operating on /
96 # 4) in a ngz operating on an alterate BE image via pkg -R
97 # (not supported yet, but we'd like to support it).
98 # 5) in a ngz operating on an linked image via pkg -R
99 # (this could be a default or user image linked to
100 # the zone.)
101 # 6) in a ngz operating on an unlinked image via pkg -R
103 # The only scenarios that we really care about in this plugin are are 2,
104 # 3, and 4. While it's tempting to try and detect these scenarios by
105 # looking at image paths, private zone files, or libbe uuids, all those
106 # methods have problems. We can't even check the image zone variant to
107 # determine if we're dealing with a zone, since in the future if we want
108 # to support user images within a zone, it's likely they will have the
109 # zone variant also set to nonglobal. There's really one way
110 # to detect if we're working on a zone image, and that is via the image
111 # metadata. Ie, either via a pkg cfg_cache linked image property, or
112 # via linked image properties exported to us by our associated parent
113 # image.
117 class LinkedImageZonePlugin(li.LinkedImagePlugin):
118 """See parent class for docstring."""
120 # default attach property values
121 attach_props_def = {
122 li.PROP_RECURSE: False
125 __zone_pkgs = frozenset([
126 frozenset(["system/zones"]),
127 frozenset(["SUNWzoner", "SUNWzoneu"])
130 def __init__(self, pname, linked):
131 """See parent class for docstring."""
132 li.LinkedImagePlugin.__init__(self, pname, linked)
134 # globals
135 self.__pname = pname
136 self.__linked = linked
137 self.__img = linked.image
138 self.__in_gz_cached = None
140 # keep track of our freshly attach children
141 self.__children = dict()
143 # cache zoneadm output
144 self.__zoneadm_list_cache = None
146 def __in_gz(self, ignore_errors=False):
147 """Check if we're executing in the global zone. Note that
148 this doesn't tell us anything about the image we're
149 manipulating, just the environment that we're running in."""
151 if self.__in_gz_cached != None:
152 return self.__in_gz_cached
154 # check if we're running in the gz
155 try:
156 self.__in_gz_cached = (_zonename() == ZONE_GLOBAL)
157 except OSError as e:
158 # W0212 Access to a protected member
159 # pylint: disable=W0212
160 if ignore_errors:
161 # default to being in the global zone
162 return True
163 raise apx._convert_error(e)
164 except apx.LinkedImageException as e:
165 if ignore_errors:
166 # default to being in the global zone
167 return True
168 raise e
170 return self.__in_gz_cached
172 def __zones_supported(self):
173 """Check to see if zones are supported in the current image.
174 i.e. can the current image have zone children."""
176 # pylint: disable=E1120
177 if DebugValues.get_value("zones_supported"):
178 return True
179 # pylint: enable=E1120
181 # first check if the image variant is global
182 variant = "variant.opensolaris.zone"
183 value = self.__img.cfg.variants[variant]
184 if value != "global":
185 return False
188 # sanity check the path to to /etc/zones. below we check for
189 # the zones packages, and any image that has the zones
190 # packages installed should have a /etc/zones file (since
191 # those packages deliver this file) but it's possible that the
192 # image was corrupted and the user now wants to be able to run
193 # pkg commands to fix it. if the path doesn't exist then we
194 # don't have any zones so just report that zones are
195 # unsupported (since zoneadm may fail to run anyway).
197 path = self.__img.root
198 if not os.path.isdir(os.path.join(path, "etc")):
199 return False
200 if not os.path.isdir(os.path.join(path, "etc/zones")):
201 return False
203 # get a set of installed packages
204 cati = self.__img.get_catalog(self.__img.IMG_CATALOG_INSTALLED)
205 pkgs_inst = frozenset([
206 stem
207 # Unused variable 'pub'; pylint: disable=W0612
208 for pub, stem in cati.pkg_names()
209 # pylint: enable=W0612
212 # check if the zones packages are installed
213 for pkgs in self.__zone_pkgs:
214 if (pkgs & pkgs_inst) == pkgs:
215 return True
217 return False
219 def __list_zones_cached(self, nocache=False, ignore_errors=False):
220 """List the zones associated with the current image. Since
221 this involves forking and running zone commands, cache the
222 results."""
224 # if nocache is set then delete any cached children
225 if nocache:
226 self.__zoneadm_list_cache = None
228 # try to return the cached children
229 if self.__zoneadm_list_cache != None:
230 assert type(self.__zoneadm_list_cache) == list
231 return self.__zoneadm_list_cache
233 # see if the target image supports zones
234 if not self.__zones_supported():
235 self.__zoneadm_list_cache = []
236 return self.__list_zones_cached()
238 # zones are only visible when running in the global zone
239 if not self.__in_gz(ignore_errors=ignore_errors):
240 self.__zoneadm_list_cache = []
241 return self.__list_zones_cached()
243 # find zones
244 try:
245 zdict = _list_zones(self.__img.root,
246 self.__linked.get_path_transform())
247 except OSError as e:
248 # W0212 Access to a protected member
249 # pylint: disable=W0212
250 if ignore_errors:
251 # don't cache the result
252 return []
253 raise apx._convert_error(e)
254 except apx.LinkedImageException as e:
255 if ignore_errors:
256 # don't cache the result
257 return []
258 raise e
260 # convert zone names into into LinkedImageName objects
261 zlist = []
262 # state is unused
263 # pylint: disable=W0612
264 for zone, (path, state) in zdict.iteritems():
265 lin = li.LinkedImageName("{0}:{1}".format(self.__pname,
266 zone))
267 zlist.append([lin, path])
269 self.__zoneadm_list_cache = zlist
270 return self.__list_zones_cached()
272 def init_root(self, root):
273 """See parent class for docstring."""
274 # nuke any cached children
275 self.__zoneadm_list_cache = None
277 def guess_path_transform(self, ignore_errors=False):
278 """See parent class for docstring."""
280 zlist = self.__list_zones_cached(nocache=True,
281 ignore_errors=ignore_errors)
282 if not zlist:
283 return li.PATH_TRANSFORM_NONE
285 # only global zones can have zone children, and global zones
286 # always execute with "/" as their root. so if the current
287 # image path is not "/", then assume we're in an alternate
288 # root.
289 root = self.__img.root.rstrip(os.sep) + os.sep
290 return (os.sep, root)
292 def get_child_list(self, nocache=False, ignore_errors=False):
293 """See parent class for docstring."""
295 inmemory = []
296 # find any newly attached zone images
297 for lin in self.__children:
298 path = self.__children[lin][li.PROP_PATH]
299 inmemory.append([lin, path])
301 ondisk = []
302 for (lin, path) in self.__list_zones_cached(nocache,
303 ignore_errors=ignore_errors):
304 if lin in [i[0] for i in inmemory]:
305 # we re-attached a zone in memory.
306 continue
307 ondisk.append([lin, path])
309 rv = []
310 rv.extend(ondisk)
311 rv.extend(inmemory)
313 for lin, path in rv:
314 assert lin.lin_type == self.__pname
316 return rv
318 def get_child_props(self, lin):
319 """See parent class for docstring."""
321 if lin in self.__children:
322 return self.__children[lin]
324 props = dict()
325 props[li.PROP_NAME] = lin
326 for i_lin, i_path in self.get_child_list():
327 if lin == i_lin:
328 props[li.PROP_PATH] = i_path
329 break
330 assert li.PROP_PATH in props
332 props[li.PROP_MODEL] = li.PV_MODEL_PUSH
333 for k, v in self.attach_props_def.iteritems():
334 if k not in props:
335 props[k] = v
337 return props
339 def attach_child_inmemory(self, props, allow_relink):
340 """See parent class for docstring."""
342 # make sure this child doesn't already exist
343 lin = props[li.PROP_NAME]
344 lin_list = [i[0] for i in self.get_child_list()]
345 assert lin not in lin_list or allow_relink
347 # cache properties (sans any temporarl ones)
348 self.__children[lin] = li.rm_dict_ent(props, li.temporal_props)
350 def detach_child_inmemory(self, lin):
351 """See parent class for docstring."""
353 # make sure this child exists
354 assert lin in [i[0] for i in self.get_child_list()]
356 # Delete this linked image
357 del self.__children[lin]
359 def sync_children_todisk(self):
360 """See parent class for docstring."""
362 # nothing to do
363 return li.LI_RVTuple(pkgdefs.EXIT_OK, None, None)
366 class LinkedImageZoneChildPlugin(li.LinkedImageChildPlugin):
367 """See parent class for docstring."""
369 def __init__(self, lic):
370 """See parent class for docstring."""
371 li.LinkedImageChildPlugin.__init__(self, lic)
373 def munge_props(self, props):
374 """See parent class for docstring."""
377 # For zones we always update the pushed child image path to
378 # be '/' (Since any linked children of the zone will be
379 # relative to that zone's root).
381 props[li.PROP_PATH] = "/"
384 def _zonename():
385 """Get the zonname of the current system."""
387 cmd = DebugValues.get_value("bin_zonename") # pylint: disable=E1120
388 if cmd is not None:
389 cmd = [cmd]
390 else:
391 cmd = ["/bin/zonename"]
393 # if the command doesn't exist then bail.
394 if not li.path_exists(cmd[0]):
395 return
397 fout = tempfile.TemporaryFile()
398 ferrout = tempfile.TemporaryFile()
399 p = pkg.pkgsubprocess.Popen(cmd, stdout=fout, stderr=ferrout)
400 p.wait()
401 if (p.returncode != 0):
402 cmd = " ".join(cmd)
403 ferrout.seek(0)
404 errout = "".join(ferrout.readlines())
405 raise apx.LinkedImageException(
406 cmd_failed=(p.returncode, cmd, errout))
408 # parse the command output
409 fout.seek(0)
410 l = fout.readlines()[0].rstrip()
411 return l
413 def _zoneadm_list_parse(line, cmd, output):
414 """Parse zoneadm list -p output. It's possible for zonepath to
415 contain a ":". If it does it will be escaped to be "\:". (But note
416 that if the zonepath contains a "\" it will not be escaped, which
417 is argubaly a bug.)"""
419 # zoneadm list output should never contain a NUL char, so
420 # temporarily replace any escaped colons with a NUL, split the string
421 # on any remaining colons, and then switch any NULs back to colons.
422 tmp_char = "\0"
423 fields = [
424 field.replace(tmp_char, ":")
425 for field in line.replace("\:", tmp_char).split(":")
428 try:
429 # Unused variable; pylint: disable=W0612
430 z_id, z_name, z_state, z_path, z_uuid, z_brand, z_iptype = \
431 fields[:7]
432 # pylint: enable=W0612
433 except ValueError:
434 raise apx.LinkedImageException(
435 cmd_output_invalid=(cmd, output))
437 return z_name, z_state, z_path, z_brand
439 def _list_zones(root, path_transform):
440 """Get the zones associated with the image located at 'root'. We
441 return a dictionary where the keys are zone names and the values are
442 tuples containing zone root path and current state. The global zone is
443 excluded from the results. Solaris10 branded zones are excluded from the
444 results."""
446 rv = dict()
447 cmd = DebugValues.get_value("bin_zoneadm") # pylint: disable=E1120
448 if cmd is not None:
449 cmd = [cmd]
450 else:
451 cmd = ["/usr/sbin/zoneadm"]
453 # if the command doesn't exist then bail.
454 if not li.path_exists(cmd[0]):
455 return rv
457 # make sure "root" has a trailing '/'
458 root = root.rstrip(os.sep) + os.sep
460 # create the zoneadm command line
461 cmd.extend(["-R", str(root), "list", "-cp"])
463 # execute zoneadm and save its output to a file
464 fout = tempfile.TemporaryFile()
465 ferrout = tempfile.TemporaryFile()
466 p = pkg.pkgsubprocess.Popen(cmd, stdout=fout, stderr=ferrout)
467 p.wait()
468 if (p.returncode != 0):
469 cmd = " ".join(cmd)
470 ferrout.seek(0)
471 errout = "".join(ferrout.readlines())
472 raise apx.LinkedImageException(
473 cmd_failed=(p.returncode, cmd, errout))
475 # parse the command output
476 fout.seek(0)
477 output = fout.readlines()
478 for l in output:
479 l = l.rstrip()
481 z_name, z_state, z_path, z_brand = \
482 _zoneadm_list_parse(l, cmd, output)
484 # skip brands that we don't care about
485 # W0511 XXX / FIXME Comments; pylint: disable=W0511
486 # XXX: don't hard code brand names, use a brand attribute
487 # pylint: enable=W0511
488 if z_brand not in ["ipkg"]:
489 continue
491 # we don't care about the global zone.
492 if (z_name == "global"):
493 continue
495 # append "/root" to zonepath
496 z_rootpath = os.path.join(z_path, "root")
497 assert z_rootpath.startswith(root), \
498 "zone path '{0}' doesn't begin with '{1}".format(
499 z_rootpath, root)
501 # If there is a current path transform in effect then revert
502 # the path reported by zoneadm to the original zone path.
503 if li.path_transform_applied(z_rootpath, path_transform):
504 z_rootpath = li.path_transform_revert(z_rootpath,
505 path_transform)
507 # we only care about zones that have been installed
508 if z_state not in zone_installed_states:
509 continue
511 rv[z_name] = (z_rootpath, z_state)
513 return rv
515 def list_running_zones():
516 """Return dictionary with currently running zones of the system in the
517 following form:
518 { zone_name : zone_path, ... }
521 zdict = _list_zones("/", li.PATH_TRANSFORM_NONE)
522 rzdict = {}
523 for z_name, (z_path, z_state) in zdict.iteritems():
524 if z_state == ZONE_STATE_STR_RUNNING:
525 rzdict[z_name] = z_path
527 return rzdict