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]
24 # Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
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.
34 # standard python 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
,
67 ZONE_STATE_STR_MOUNTED
,
68 ZONE_STATE_STR_RUNNING
,
69 ZONE_STATE_STR_SHUTTING_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
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
117 class LinkedImageZonePlugin(li
.LinkedImagePlugin
):
118 """See parent class for docstring."""
120 # default attach property values
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
)
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
156 self
.__in
_gz
_cached
= (_zonename() == ZONE_GLOBAL
)
158 # W0212 Access to a protected member
159 # pylint: disable=W0212
161 # default to being in the global zone
163 raise apx
._convert
_error
(e
)
164 except apx
.LinkedImageException
as e
:
166 # default to being in the global zone
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"):
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":
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")):
200 if not os
.path
.isdir(os
.path
.join(path
, "etc/zones")):
203 # get a set of installed packages
204 cati
= self
.__img
.get_catalog(self
.__img
.IMG_CATALOG_INSTALLED
)
205 pkgs_inst
= frozenset([
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
:
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
224 # if nocache is set then delete any cached children
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
()
245 zdict
= _list_zones(self
.__img
.root
,
246 self
.__linked
.get_path_transform())
248 # W0212 Access to a protected member
249 # pylint: disable=W0212
251 # don't cache the result
253 raise apx
._convert
_error
(e
)
254 except apx
.LinkedImageException
as e
:
256 # don't cache the result
260 # convert zone names into into LinkedImageName objects
263 # pylint: disable=W0612
264 for zone
, (path
, state
) in zdict
.iteritems():
265 lin
= li
.LinkedImageName("{0}:{1}".format(self
.__pname
,
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
)
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
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."""
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
])
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.
307 ondisk
.append([lin
, path
])
314 assert lin
.lin_type
== self
.__pname
318 def get_child_props(self
, lin
):
319 """See parent class for docstring."""
321 if lin
in self
.__children
:
322 return self
.__children
[lin
]
325 props
[li
.PROP_NAME
] = lin
326 for i_lin
, i_path
in self
.get_child_list():
328 props
[li
.PROP_PATH
] = i_path
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():
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."""
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
] = "/"
385 """Get the zonname of the current system."""
387 cmd
= DebugValues
.get_value("bin_zonename") # pylint: disable=E1120
391 cmd
= ["/bin/zonename"]
393 # if the command doesn't exist then bail.
394 if not li
.path_exists(cmd
[0]):
397 fout
= tempfile
.TemporaryFile()
398 ferrout
= tempfile
.TemporaryFile()
399 p
= pkg
.pkgsubprocess
.Popen(cmd
, stdout
=fout
, stderr
=ferrout
)
401 if (p
.returncode
!= 0):
404 errout
= "".join(ferrout
.readlines())
405 raise apx
.LinkedImageException(
406 cmd_failed
=(p
.returncode
, cmd
, errout
))
408 # parse the command output
410 l
= fout
.readlines()[0].rstrip()
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.
424 field
.replace(tmp_char
, ":")
425 for field
in line
.replace("\:", tmp_char
).split(":")
429 # Unused variable; pylint: disable=W0612
430 z_id
, z_name
, z_state
, z_path
, z_uuid
, z_brand
, z_iptype
= \
432 # pylint: enable=W0612
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
447 cmd
= DebugValues
.get_value("bin_zoneadm") # pylint: disable=E1120
451 cmd
= ["/usr/sbin/zoneadm"]
453 # if the command doesn't exist then bail.
454 if not li
.path_exists(cmd
[0]):
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
)
468 if (p
.returncode
!= 0):
471 errout
= "".join(ferrout
.readlines())
472 raise apx
.LinkedImageException(
473 cmd_failed
=(p
.returncode
, cmd
, errout
))
475 # parse the command output
477 output
= fout
.readlines()
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"]:
491 # we don't care about the global zone.
492 if (z_name
== "global"):
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(
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
,
507 # we only care about zones that have been installed
508 if z_state
not in zone_installed_states
:
511 rv
[z_name
] = (z_rootpath
, z_state
)
515 def list_running_zones():
516 """Return dictionary with currently running zones of the system in the
518 { zone_name : zone_path, ... }
521 zdict
= _list_zones("/", li
.PATH_TRANSFORM_NONE
)
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