|
|
/usr/lib/python2.7/site-packages/openstack/compute/v22 ] h+ Z3 H# M: w
server.py
( M- x' x @" f* i8 |) J文件内容:
# z0 K! o% ]* v1 J A; N" z' ^6 R1 O+ U/ H2 q+ S4 D
# Licensed under the Apache License, Version 2.0 (the "License"); you may
: x$ T* ^- F C( n- @4 W2 M+ O# not use this file except in compliance with the License. You may obtain$ T5 b/ B3 \7 O" c1 b" C
# a copy of the License at B) m! |% G* x( k3 a. @5 g
#, F/ ^5 g$ E0 Z; x8 p: Z
# http://www.apache.org/licenses/LICENSE-2.0" W% ~$ {3 @- ^3 Y* ? R2 c7 ]9 p
#- Q6 h. c3 f( O. O
# Unless required by applicable law or agreed to in writing, software* u6 O3 X% [5 R [
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
: \% y f2 }$ `# }; I: n) r# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
3 K* ^, w8 `9 D" n+ I9 l: H2 z# License for the specific language governing permissions and limitations
3 V% ?; ^! v5 g: |* n: o# under the License.
5 @! w3 Y/ I$ Y$ pfrom openstack.compute.v2 import metadata
3 w6 h* v( g! y9 D6 O0 n6 K4 D' dfrom openstack.image.v2 import image
+ x# V' e" n7 Vfrom openstack import exceptions
. r9 C5 D, k2 k1 U, }8 z! sfrom openstack import resource: {2 ^' z5 F0 I; J* B; q: @4 U4 J
from openstack import utils
/ g, Z: [6 q c. cCONSOLE_TYPE_ACTION_MAPPING = {8 Y, p3 L6 J- E& r5 s% S% V6 Q
'novnc': 'os-getVNCConsole',
7 x4 p8 ?/ R, g- | 'xvpvnc': 'os-getVNCConsole',0 m5 Q9 r+ J) ^6 E, Z7 y7 e
'spice-html5': 'os-getSPICEConsole',7 I d* j+ y" x
'rdp-html5': 'os-getRDPConsole',
3 Q$ @. }* `; Z7 W7 ~% ^ 'serial': 'os-getSerialConsole'% Z6 ?" S% f, X! o$ f0 h" `' `2 i M
}
* d: v* w& J6 \4 e! t9 Yclass Server(resource.Resource, metadata.MetadataMixin, resource.TagMixin):
# X @& ?5 Y4 T( j resource_key = 'server'5 t, {* y1 R8 V) X4 v$ _
resources_key = 'servers') k) N) e. H- X6 p. Y: j; a
base_path = '/servers'
5 {+ t6 u9 e; X: j: Y6 n # capabilities
+ P$ \ x) H" r5 b allow_create = True
, e; |1 \1 J [& _6 G3 S allow_fetch = True
. T( [. K1 r1 U3 z allow_commit = True/ v9 l, y1 \3 r$ h% N
allow_delete = True
8 m9 E, M* Y4 _* k' K9 h- v6 H4 F+ j) w allow_list = True& w; H8 D. N O% b$ ]4 L5 K
_query_mapping = resource.QueryParameters(2 H; ?+ L" _) ~
"auto_disk_config", "availability_zone",) m, ]5 L% J6 y0 w* M- N8 {' x6 ^
"created_at", "description", "flavor"," l( s+ z2 G" z" a' k
"hostname", "image", "kernel_id", "key_name",
( T& \* l7 b3 Q$ D$ _3 t "launch_index", "launched_at", "locked_by", "name",
M" N7 \- `9 u "node", "power_state", "progress", "project_id", "ramdisk_id",
1 ^; I& [% w5 `. f "reservation_id", "root_device_name",
. Z2 r+ p" O# f "status", "task_state", "terminated_at", "user_id",
6 O5 v9 j1 }8 A+ C" {' Z0 | "vm_state",
+ ?) K4 u9 R' v1 j3 H3 }7 z* c "sort_key", "sort_dir",' I! D) f; e- G0 f B- {
access_ipv4="access_ip_v4",
@& K# v g0 |9 V access_ipv6="access_ip_v6",) T3 g, H, J, q: Q! N$ o. @) z
has_config_drive="config_drive",3 }. p: r; ?7 t9 t j/ |& M( U- |
deleted_only="deleted",
5 _$ o! U0 f: e5 ]9 u' P- Q compute_host="host",
! D8 d8 F7 W3 N is_soft_deleted="soft_deleted",3 R4 x" f3 T: q5 T3 R. T0 a
ipv4_address="ip",. r, C- o7 h4 H& M' V
ipv6_address="ip6",
4 v+ `8 [4 ]4 b, Q6 r* v0 X changes_since="changes-since",: L ^# W- B% T% b* ]* i6 n: [. w
changes_before="changes-before",
7 z w4 l: A# V* u id="uuid",
* K4 h. O. w& N3 } all_projects="all_tenants",% V+ y# I2 D* Y: e% A* g% d
**resource.TagMixin._tag_query_parameters/ K& h3 c: u8 `0 G8 V
)
# R- G6 F. }7 J7 Z; }" P _max_microversion = '2.72' i' F% @3 z: I9 Q: s
#: A list of dictionaries holding links relevant to this server.
" o" V! ^3 k7 L& k1 d ? links = resource.Body('links')
6 m3 I" U; l+ W9 ?5 V1 U$ L access_ipv4 = resource.Body('accessIPv4')
* E, W9 J2 y+ s9 n access_ipv6 = resource.Body('accessIPv6')
* t! H" y9 _) T* `$ L; S #: A dictionary of addresses this server can be accessed through.2 {# N, t. `6 L/ K; ~0 h( ]5 ?) L1 i
#: The dictionary contains keys such as ``private`` and ``public``,
& [- H% l; E' H5 S# L #: each containing a list of dictionaries for addresses of that type.
- X1 R' R) i( d. d" g #: The addresses are contained in a dictionary with keys ``addr`` l2 i, Q. G) \' @1 V& x* r
#: and ``version``, which is either 4 or 6 depending on the protocol/ {* a! {; j, ^7 ~& I0 l& p3 m& {3 E
#: of the IP address. *Type: dict*3 a" G$ c+ [4 X9 I0 i
addresses = resource.Body('addresses', type=dict)
/ H* [ W8 m1 K) k #: When a server is first created, it provides the administrator password.
! C4 s# L/ C) s( a2 a% l admin_password = resource.Body('adminPass')) A2 I7 v0 U% ~
#: A list of an attached volumes. Each item in the list contains at least* H% I1 m1 L' Y/ J
#: an "id" key to identify the specific volumes.3 d0 `! c$ d. T( E5 |8 X' _ R
attached_volumes = resource.Body(
! h# _2 t! ~' E" }; E$ V! W 'os-extended-volumes:volumes_attached')
8 i2 S' ]% ?, O) [) \' { #: The name of the availability zone this server is a part of.
4 M: p8 j6 k+ m0 ? availability_zone = resource.Body('OS-EXT-AZ:availability_zone')/ ?" g+ B6 {6 V# g" D1 G, ]
#: Enables fine grained control of the block device mapping for an
9 o3 {1 l" ^, L #: instance. This is typically used for booting servers from volumes.0 D9 O% c' `) Y* q$ D
block_device_mapping = resource.Body('block_device_mapping_v2')
/ K% ^7 G3 _: w) E7 ~9 C$ I #: Indicates whether or not a config drive was used for this server.
$ K& ?- D: T' `; L% v config_drive = resource.Body('config_drive')
( w& Z9 l; Z' ~ K# T, v3 C6 F #: The name of the compute host on which this instance is running.4 R& k4 M3 ?" {2 H( B0 j& y
#: Appears in the response for administrative users only.
! w% x1 d# n/ }: H7 t5 Y# y# ? compute_host = resource.Body('OS-EXT-SRV-ATTR:host')4 S" \+ M' D7 z0 Q
#: Timestamp of when the server was created.. r( r" F& R% K7 D( \1 l* z( `1 L
created_at = resource.Body('created')
$ u9 a" h& S- Y( ^) j6 C2 O4 w #: The description of the server. Before microversion& S0 k' e6 j5 Q- m; T! L. M
#: 2.19 this was set to the server name." g5 s, r4 U0 [4 A
description = resource.Body('description')5 k+ j2 q/ M2 e! ~( H
#: The disk configuration. Either AUTO or MANUAL.( o" r+ _0 B0 N
disk_config = resource.Body('OS-DCF:diskConfig'), {) f$ m3 B( @, A2 C, m6 l, \; x5 M4 B
#: The flavor reference, as a ID or full URL, for the flavor to use for; z7 H; X. v' P0 i4 S2 M
#: this server.. [; m& A; G$ K1 i! \8 B* ^
flavor_id = resource.Body('flavorRef')3 a. F* U0 [/ a& A. ~* D' D
#: The flavor property as returned from server.
( E. x( V3 r$ k # TODO(gtema): replace with flavor.Flavor addressing flavor.original_name. V% U/ r8 j) a9 I
flavor = resource.Body('flavor', type=dict)
; L9 {& s: c# e' i0 |" ~( F7 a #: Indicates whether a configuration drive enables metadata injection.: X9 w! ^9 L, r7 R& E% u/ K6 {
#: Not all cloud providers enable this feature.
4 m( Q9 U9 A% \: r has_config_drive = resource.Body('config_drive')" X, B. f; _3 }8 |; n3 f
#: An ID representing the host of this server.
' r, _# k, g8 f" c% U' f0 p* R6 S1 w host_id = resource.Body('hostId')
" M8 s6 i0 ^0 V5 J #: The host status.
* D! l4 a) W. Y( f; L% P0 ~5 M% d host_status = resource.Body('host_status')
4 z# K; {! R0 Z; Q7 L2 G- k. v# H# Z4 _ #: The hostname set on the instance when it is booted.
) u/ S& H: Z! e5 j4 ~. x #: By default, it appears in the response for administrative users only.
$ l2 p9 {$ v+ T: R hostname = resource.Body('OS-EXT-SRV-ATTR:hostname')
, T6 X u4 S# T% B) c, K, c #: The hypervisor host name. Appears in the response for administrative
4 V# ^% r; Z4 {: j #: users only.8 t$ J3 y( n0 V9 \
hypervisor_hostname = resource.Body('OS-EXT-SRV-ATTR:hypervisor_hostname')! W% \& [; ?, o4 k' b, }
#: The image reference, as a ID or full URL, for the image to use for
, g; i6 |4 \( p7 r #: this server.+ E' S5 E) _7 Z' X, @- s$ N
image_id = resource.Body('imageRef')1 E/ B& y: R( |3 q
#: The image property as returned from server.
# ~7 ]& p" b% u9 G. |4 n: d image = resource.Body('image', type=image.Image)+ z2 Q, \' ?, E5 U
#: The instance name. The Compute API generates the instance name from the1 g6 V1 A+ Q* i; ^" U/ W
#: instance name template. Appears in the response for administrative users4 k. J* F& l4 ^; p6 B8 [. ?
#: only.8 K# }" o! v+ C" H/ u* U
instance_name = resource.Body('OS-EXT-SRV-ATTR:instance_name')
- O: O; U# x2 l! ? # The locked status of the server1 a2 v/ O+ J; |" d' d% C
is_locked = resource.Body('locked', type=bool)
! b) P, S2 G+ Y/ A! a #: The UUID of the kernel image when using an AMI. Will be null if not.9 H. | F) c) a, h% x( Y& N
#: By default, it appears in the response for administrative users only.7 {6 Y7 E- O" y) H/ \$ S
kernel_id = resource.Body('OS-EXT-SRV-ATTR:kernel_id')9 i r. M6 p" P! {2 Y
#: The name of an associated keypair
+ ~3 [5 ^' R# O9 ^! N key_name = resource.Body('key_name')
J' W2 x0 e1 ~6 O F1 N5 G #: When servers are launched via multiple create, this is the0 W2 M; ^. V" Q2 h1 }
#: sequence in which the servers were launched. By default, it
; Q z# Z( K* K. ~ #: appears in the response for administrative users only.' }; |" U" G% F ~ W
launch_index = resource.Body('OS-EXT-SRV-ATTR:launch_index', type=int)
" h9 F- S, P- V, ]" {* M #: The timestamp when the server was launched.
" D: |$ L+ G* h7 [/ N3 A, m launched_at = resource.Body('OS-SRV-USG:launched_at')! S7 t" U. y+ T9 f
#: Metadata stored for this server. *Type: dict*( _. ^# {2 }' C) E) n9 z: B7 n: w
metadata = resource.Body('metadata', type=dict)& H% F. S9 w* b% z/ K( W) c
#: A networks object. Required parameter when there are multiple
8 [9 j( |+ {; X1 S- z3 Y( l* U #: networks defined for the tenant. When you do not specify the
) G* n k1 d3 c3 T8 o. Q, M/ b #: networks parameter, the server attaches to the only network" A0 j) M2 o5 ~; [8 c4 h
#: created for the current tenant.% Z7 B0 j) F' w- c; K" d4 b
networks = resource.Body('networks'), w* N# X( w0 X* r
#: The file path and contents, text only, to inject into the server at
2 v0 v. l/ O1 b# J #: launch. The maximum size of the file path data is 255 bytes.
0 ~: ~% m* t) l3 P #: The maximum limit is The number of allowed bytes in the decoded,
( |) `: o7 a, m( N% p8 C! U #: rather than encoded, data.% l( {- ~5 x: M
personality = resource.Body('personality')/ ^2 c+ i6 }! l1 _6 |
#: The power state of this server.
: d" ]/ \9 Q- o M- ~" v) h3 F power_state = resource.Body('OS-EXT-STS:power_state')
4 ^8 B9 f; Q% W! t #: While the server is building, this value represents the percentage* ^* q& W+ G- k: e' C {/ v' W
#: of completion. Once it is completed, it will be 100. *Type: int*
5 E' Y* Q) Y# f" J/ n3 M progress = resource.Body('progress', type=int). S9 ]' b% \& D
#: The ID of the project this server is associated with.$ a( {$ ?9 u# e
project_id = resource.Body('tenant_id')
?2 J/ Y% J( _% Q V* r4 C #: The UUID of the ramdisk image when using an AMI. Will be null if not.
$ e0 @! z8 e/ D2 E7 o$ h) u+ J1 b #: By default, it appears in the response for administrative users only.
$ y! R( Z" _' N! x% S; m ramdisk_id = resource.Body('OS-EXT-SRV-ATTR:ramdisk_id')% w' a+ a8 Q! ?, |7 Y& c
#: The reservation id for the server. This is an id that can be" u" H5 h' ~& s& K4 V+ X* S
#: useful in tracking groups of servers created with multiple create,
, ]( o0 D- }' N% X #: that will all have the same reservation_id. By default, it appears
; u' ^( J; M* Z m& j) @1 o4 [ #: in the response for administrative users only.( M' X5 Y! U l- F# C9 |( i
reservation_id = resource.Body('OS-EXT-SRV-ATTR:reservation_id')
' i( Q8 ?6 K: S$ f# N2 y! w #: The root device name for the instance By default, it appears in the
% G- @$ m% ^2 p+ E6 S: k #: response for administrative users only.
M" d( u" D$ h7 S: }' R; M) O, _8 W root_device_name = resource.Body('OS-EXT-SRV-ATTR:root_device_name')
1 V, H b6 |, Q2 \ #: The dictionary of data to send to the scheduler.% T; Z0 _/ @* K
scheduler_hints = resource.Body('OS-SCH-HNT:scheduler_hints', type=dict)
: R7 s5 Z2 V. n" ^' c8 U5 X9 x7 E #: A list of applicable security groups. Each group contains keys for
g$ q7 n5 W4 b' F #: description, name, id, and rules.: s0 I% [) O* _5 s4 g. W
security_groups = resource.Body('security_groups',
a* S" G, i$ V; B9 _: q type=list, list_type=dict)
( |9 e) d2 M& M3 n7 S #: The UUIDs of the server groups to which the server belongs.
7 _8 f$ c( H9 ~: k2 u' X v- p( n1 O #: Currently this can contain at most one entry." s/ c' ^. u6 t4 d3 S/ q* B5 q
server_groups = resource.Body('server_groups', type=list, list_type=dict)4 t- k8 U+ V1 c4 i! z/ y
#: The state this server is in. Valid values include ``ACTIVE``,$ h4 Q# X: [* K. ~* R9 f# |4 H1 \
#: ``BUILDING``, ``DELETED``, ``ERROR``, ``HARD_REBOOT``, ``PASSWORD``," u6 }. p9 Q- ?9 @3 W+ ^1 {
#: ``PAUSED``, ``REBOOT``, ``REBUILD``, ``RESCUED``, ``RESIZED``,$ @! H" Q6 l- z* t
#: ``REVERT_RESIZE``, ``SHUTOFF``, ``SOFT_DELETED``, ``STOPPED``,
5 z0 R% W6 E+ P/ K1 } #: ``SUSPENDED``, ``UNKNOWN``, or ``VERIFY_RESIZE``.7 ]; [, {7 f- L8 q* f. }; C! Y
status = resource.Body('status')
5 r; B' X/ K+ T #: The task state of this server." C) B7 o v+ N' {
task_state = resource.Body('OS-EXT-STS:task_state')" f5 k5 s" c( k4 B+ `
#: The timestamp when the server was terminated (if it has been).( J% W2 ?3 I8 y8 T( {+ }
terminated_at = resource.Body('OS-SRV-USG:terminated_at')
. d2 {9 K! H( `: o' ~6 ^ #: A list of trusted certificate IDs, that were used during image& | m. r& X2 x; |3 j8 ~
#: signature verification to verify the signing certificate.# S4 P6 ]7 ]$ G, L8 }
trusted_image_certificates = resource.Body(4 t+ U5 o) Q: o0 @: F5 O- d
'trusted_image_certificates', type=list)
' f6 \' ?8 w% i #: Timestamp of when this server was last updated.
: m! ]$ W& N/ U updated_at = resource.Body('updated')( ? A& _- h. }: p% o
#: Configuration information or scripts to use upon launch.; w* Z0 c2 m X; e+ X5 u
#: Must be Base64 encoded. @9 D& O% ^: u# l; U5 e& j
user_data = resource.Body('OS-EXT-SRV-ATTR:user_data'). H* D* r/ i: f8 Z1 W' U# E ]% ~
#: The ID of the owners of this server.6 ~ e/ K: F2 Q* s! G" [5 ^9 ]
user_id = resource.Body('user_id')5 } Q% R" l7 [( v, d" E- [3 N
#: The VM state of this server.
! c+ @6 K1 x9 E! s# G vm_state = resource.Body('OS-EXT-STS:vm_state')
; R- o( ~9 V2 L# L7 _ {: K; K def _prepare_request(self, requires_id=True, prepend_key=True,3 P' g* [8 G; J) s/ d+ _
base_path=None, **kwargs):
0 i& G* V' Y2 n$ \+ `0 q1 x9 ]) ~ request = super(Server, self)._prepare_request(requires_id=requires_id,
$ h3 v/ h5 |- H V- X4 o6 y prepend_key=prepend_key,
2 G; x, ^1 u A base_path=base_path)
9 V2 j6 V6 r4 W6 {0 b, ` server_body = request.body[self.resource_key]
! c8 U8 V7 @0 o" D # Some names exist without prefix on requests but with a prefix3 q4 [9 G( v- e! ]) g
# on responses. If we find that we've populated one of these' j2 } v% C' L: K, R
# attributes with something and then go to make a request, swap out9 N* D) o' }( d9 x6 d. g# w! d
# the name to the bare version.
f; u5 G q% j4 z # Availability Zones exist with a prefix on response, but not request7 H& w' b G3 C) S5 _. N
az_key = "OS-EXT-AZ:availability_zone"
2 z* C8 k4 r0 X" k9 P6 c if az_key in server_body:4 o+ m/ j0 F7 w1 g1 j. Z
server_body["availability_zone"] = server_body.pop(az_key)- ^, y( x6 U7 L
# User Data exists with a prefix on response, but not request/ |6 ]) Z) I6 g- n( g0 N
ud_key = "OS-EXT-SRV-ATTR:user_data"
5 w) F- S1 U$ t5 h& d7 u7 ^ if ud_key in server_body:
w/ S! v1 A* k8 s! O server_body["user_data"] = server_body.pop(ud_key). m" K z/ F) L) n1 {
# Scheduler hints are sent in a top-level scope, not within the5 c- p4 ]' _% D$ s
# resource_key scope like everything else. If we try to send
& c& ~3 H: r) L, N2 J* O; j" c # scheduler_hints, pop them out of the resource_key scope and into
9 ? {9 H8 P8 l) P3 W # their own top-level scope.
6 o+ w' r9 Y% c: i$ s6 B& n! e hint_key = "OS-SCH-HNT:scheduler_hints"5 S. Y8 B/ u2 N; U
if hint_key in server_body:4 E4 R7 y, q6 D' q9 Z, e* {% O! w0 H
request.body[hint_key] = server_body.pop(hint_key) }6 x6 j; }, m# |7 Q5 p
return request
$ K! s a) h/ H. C1 b* j$ n' O def _action(self, session, body, microversion=None):
1 W! D+ z) X: H( T7 E """Preform server actions given the message body."""
4 K' s6 t/ W2 ^ # NOTE: This is using Server.base_path instead of self.base_path
3 |( ^$ E6 Y+ C # as both Server and ServerDetail instances can be acted on, but3 v; s1 _) q: X& b
# the URL used is sans any additional /detail/ part.* b# P, C6 h. o" J) P
url = utils.urljoin(Server.base_path, self.id, 'action')# ]+ |$ S' y0 ]
headers = {'Accept': ''}
1 \3 c; P% L; b$ v response = session.post(
# L0 p6 A" O: R0 D) g url, json=body, headers=headers, microversion=microversion)6 J3 [. I2 [4 r5 U
exceptions.raise_from_response(response)! s, h2 T: X: ?& E+ s
return response
3 ]% g, @) ` s/ B& N def change_password(self, session, new_password):
) f( q8 m$ |# ~& W """Change the administrator password to the given password."""7 X7 B* O& X! t, N1 e0 t
body = {'changePassword': {'adminPass': new_password}}
1 }" `$ c9 c4 l. K& h self._action(session, body)
; u: P f! C4 ?5 R! n. j& @3 b* |# K b def get_password(self, session):
3 O* o8 ?4 L* X2 K) Y* e/ F5 i( G """Get the encrypted administrator password."""
, t5 v. S) ^5 [* V+ @ url = utils.urljoin(Server.base_path, self.id, 'os-server-password')2 u+ d7 b) l- {9 |' f* s
return session.get(url)& U( _' X+ W8 }* o, S
def reboot(self, session, reboot_type):8 D M) C! i4 `9 W1 q8 ~' Q
"""Reboot server where reboot_type might be 'SOFT' or 'HARD'."""
3 i Z: v; a9 n# _& U body = {'reboot': {'type': reboot_type}}
6 Q, U8 T% I* f2 I$ `; p self._action(session, body)
% B- |3 R( A4 L: I/ B5 r def force_delete(self, session):
- l9 ` x: ^' |( o: ` """Force delete a server."""
0 V6 }( Y7 e. t body = {'forceDelete': None}! _+ W8 }/ r8 U
self._action(session, body)3 x* S( L! y4 p4 n+ |1 V
def rebuild(self, session, name, admin_password,
% b( `& M j$ K) {* A; e preserve_ephemeral=False, image=None,4 x9 W) F' a) `) a/ R* x2 s* E
access_ipv4=None, access_ipv6=None,0 i0 F- a- `% T6 B/ m9 ^+ W& w- w
metadata=None, personality=None):( I3 @+ J/ B$ J4 h+ F
"""Rebuild the server with the given arguments."""8 P8 }& W9 D) ^- K5 U; l: ?
action = {' f6 T% _5 z1 L
'name': name,
" `8 `' `8 W/ G4 w9 \ 'adminPass': admin_password,
, f+ L& W% |2 w3 c6 F- ]% K% | 'preserve_ephemeral': preserve_ephemeral
; ]) e+ E/ X1 b3 H }
$ X* _ {( W, X! g M% f5 ^, s if image is not None:: y( ^8 n% ~* s9 f2 q
action['imageRef'] = resource.Resource._get_id(image)
, s2 J* q( M0 Y1 I, H- Q if access_ipv4 is not None:, O+ `! \) \7 c
action['accessIPv4'] = access_ipv41 W+ }$ {3 i0 U* l( U v. F' c
if access_ipv6 is not None:( _" u- ^5 O- R" v2 U
action['accessIPv6'] = access_ipv6
1 e: t* B! j J7 q6 b' O if metadata is not None:% N$ F" B* b( X3 f
action['metadata'] = metadata2 T; Z- Z7 `0 A7 D( E* y& K6 X' q2 w
if personality is not None:6 Q7 Q# h& Q. h5 Q
action['personality'] = personality5 H7 f3 i, i! S
body = {'rebuild': action}2 p$ k' B, {3 O
response = self._action(session, body)) X; @# K; D2 J Q
self._translate_response(response)4 h8 Q$ z' _; P7 {# [* r
return self* \2 B) v$ ?/ A6 u6 A
def resize(self, session, flavor):
n: n% ]! u s* u8 |4 q$ z """Resize server to flavor reference."""
8 G0 t4 T3 [0 Z9 m8 k! S body = {'resize': {'flavorRef': flavor}}
! ]6 p. j9 B5 ~0 Z- s/ Q: [ self._action(session, body)
- Q/ M& q0 C+ J1 }- ]1 L def confirm_resize(self, session):3 J7 w, p9 U7 c! V: `. S
"""Confirm the resize of the server."""3 d+ {$ p f4 u, ]" M$ H
body = {'confirmResize': None}7 T! H+ M1 z2 x6 w7 S0 q
self._action(session, body)+ X3 a5 k4 g4 W4 O" X
def revert_resize(self, session):8 \2 e- \4 V+ M7 O( r* A
"""Revert the resize of the server."""1 ^) L7 h% x5 A' i" @) J
body = {'revertResize': None}) O0 M0 O- \/ B: B R
self._action(session, body)! U2 [3 _+ A) T. B
def create_image(self, session, name, metadata=None):# H* o. N5 m$ m; A* X! \: i- \' M
"""Create image from server."""/ L8 o6 Z( H; t/ V6 P1 C Q
action = {'name': name}
) O$ Q4 s: Q( l/ H' k if metadata is not None:
! j E! _1 N/ b/ s, t/ T5 o. C action['metadata'] = metadata6 W9 v1 w1 B# I* W3 T
body = {'createImage': action}
{- D6 i6 d2 E* g. o # You won't believe it - wait, who am I kidding - of course you will!* k6 }, |. Z: G N' e9 y( x' G
# Nova returns the URL of the image created in the Location% Y8 D9 T7 g2 Q0 G2 N) s
# header of the response. (what?) But, even better, the URL it responds
6 [; v9 @. q: A% l" ^" F2 K # with has a very good chance of being wrong (it is built from' H" _' X: H2 J; e# K& c
# nova.conf values that point to internal API servers in any cloud1 J+ I( e& C& ^: b' k
# large enough to have both public and internal endpoints.
" c1 J) p0 I! r B) j+ _5 Z1 ~ # However, nobody has ever noticed this because novaclient doesn't% |& J1 [6 K& k2 c3 G* E5 X
# actually use that URL - it extracts the id from the end of
# i& q( m4 R) `3 M# T# o # the url, then returns the id. This leads us to question:
L' n: h: j1 B$ l # a) why Nova is going to return a value in a header
8 A& Z u" Q/ [- A2 k% K # b) why it's going to return data that probably broken1 Y ?5 V# S& R0 _9 v" h- W: }, x
# c) indeed the very nature of the fabric of reality% s/ j8 e# }. y% N6 z
# Although it fills us with existential dread, we have no choice but
1 l: d1 C# _) h# n1 }; N1 R # to follow suit like a lemming being forced over a cliff by evil4 f& A* V7 m3 w/ F
# producers from Disney.
' p5 V+ e' T0 N" Z6 [3 r7 U6 s microversion = None2 i3 Y9 X) s! k) Z- E3 ?0 U0 W8 A
if utils.supports_microversion(session, '2.45'):
6 o* ~' p9 Q. _- \3 V3 N microversion = '2.45'
1 |& K& ?5 k% \+ e. N, ` response = self._action(session, body, microversion)
" T0 J# e% X6 Y body = None" u) }5 u% ?' q; X3 y4 U W
try:
0 Y6 C- D/ ?( E% C1 d% ] # There might be body, might be not
0 l- P! K, b4 M$ x1 R+ y body = response.json()+ l7 V% L# T1 u; v# B- \. [, d5 }: t
except Exception:7 F6 }$ P* ]4 v/ m
pass- @* n( l! E' ]1 j7 L
if body and 'image_id' in body:. ^- e& ?" Q; l/ |) V
image_id = body['image_id']
1 O K- U- O1 i _; o0 B% s0 P else:
# F1 p! p1 F7 Q. e$ Y9 Z$ { E6 z, D image_id = response.headers['Location'].rsplit('/', 1)[1]
8 T# E! P) j# Q7 T; G return image_id5 N! K6 l& Y3 S5 t. l6 X2 h0 f
def add_security_group(self, session, security_group_name):. O/ w# e& u; n H3 Q
body = {"addSecurityGroup": {"name": security_group_name}}
% |* q( H1 y9 ^# {! L' K3 m; |+ W8 I6 { self._action(session, body)
9 Y' ?( q- C, N5 F& x- h& ?) l def remove_security_group(self, session, security_group_name):
( u4 o2 b/ w+ _0 _) }! Z! Q& W" E/ I body = {"removeSecurityGroup": {"name": security_group_name}}( A$ Z2 _) b: [! H4 z) y% w
self._action(session, body)7 \' N8 k9 P0 }( N9 V! u
def reset_state(self, session, state):4 k' @! Z- k$ I' ?: y5 V( B% I4 p
body = {"os-resetState": {"state": state}}, t- I. g! S9 |* X$ ~: r
self._action(session, body)
' [7 n6 l, M% P( P( P2 @% @ def add_fixed_ip(self, session, network_id):
4 l3 B# Z/ \$ ^. T6 t) v body = {"addFixedIp": {"networkId": network_id}}3 s, i( \) w4 S; k
self._action(session, body)
, ~3 f0 g8 U. o. k& o0 ]6 A% U def remove_fixed_ip(self, session, address):
7 g% j3 v# f M9 [' X; {) J+ E body = {"removeFixedIp": {"address": address}}
! G) }" |" t7 q* [- C1 c self._action(session, body)3 ~! }4 {( o$ W4 T2 q
def add_floating_ip(self, session, address, fixed_address=None):
. L8 q$ I& L$ M: _ body = {"addFloatingIp": {"address": address}}
' I3 `8 L3 \4 v" y if fixed_address is not None:4 j' z3 q$ i' M0 F5 D
body['addFloatingIp']['fixed_address'] = fixed_address n4 d& h5 {+ H# }' I! \* c
self._action(session, body)
( ]9 F: v/ q+ R( Y8 x8 y def remove_floating_ip(self, session, address): r! W$ q( M3 u7 h% I% a3 M) q
body = {"removeFloatingIp": {"address": address}}
" Y( f. I# d" a, ] self._action(session, body); x1 @3 C- b/ i/ X3 a
def backup(self, session, name, backup_type, rotation):
7 L3 E+ K( ^3 S4 w$ r" A+ y body = {0 S# o4 }+ i2 X& ^
"createBackup": {
" |5 e6 N1 W/ z8 n "name": name,9 O' n8 y( J1 G
"backup_type": backup_type,
- z" q; C7 a: o2 ~. u "rotation": rotation
% M+ w: }6 R N- F: x, V7 j }, g+ U8 v8 y+ }: \/ V- e
}4 K+ e5 F# [! ]6 P' I8 `: @$ i J) K
self._action(session, body)
( g( Q9 V8 e( x# i! U def pause(self, session):
* Z5 y+ j# Q. Z0 l body = {"pause": None}; H2 \% U# k$ w) M' Z) a1 Y- e
self._action(session, body)$ G' j) d: Z# I2 H& @
def unpause(self, session):" t* e1 i$ w. Z* p
body = {"unpause": None}9 P$ ^, {6 B! t- W$ u
self._action(session, body)2 ~. s3 |4 L5 L- `" r7 q! s
def suspend(self, session):
- H, j' C- N! y' `; W# r& Q, k# p% P body = {"suspend": None}
5 `' |. n! a$ _ self._action(session, body)$ G( K. Z9 ?5 {: ]$ f
def resume(self, session):- v1 @# q9 A8 M, N' G
body = {"resume": None}
& w6 ?) K$ g5 L) I self._action(session, body)
' v/ V# [1 |. Z1 _) B def lock(self, session):6 M3 J" |" J/ Q+ d+ s1 W! }
body = {"lock": None}
: N2 U) t1 O. j% i4 ` \ self._action(session, body)& ]4 r0 D4 Y5 T6 ~
def unlock(self, session):
% D6 C/ S$ Z& f# I! O" b body = {"unlock": None}
# I& Z5 W4 i; A8 y$ x2 a- Y$ z self._action(session, body). I( B- g* J. l0 H
def rescue(self, session, admin_pass=None, image_ref=None):$ V. z' C' ]4 i' _* x& \
body = {"rescue": {}}
! s8 G2 H6 T: V9 _$ n+ d6 i& [ if admin_pass is not None:
+ D" C1 B! y+ `& Z# I8 S0 X body["rescue"]["adminPass"] = admin_pass
* c1 ^/ f2 G! R7 D4 @# x: Z4 W: S2 o if image_ref is not None:
% R4 ~2 [5 K% C2 F9 w) j4 A7 s body["rescue"]["rescue_image_ref"] = image_ref* y3 H6 U% m& }
self._action(session, body) \! h$ d: [3 \
def unrescue(self, session):8 M V& o8 R; O% F5 C8 z2 x
body = {"unrescue": None}" \# S" h3 h+ s7 G4 |& h( M0 K
self._action(session, body)
0 M2 t7 T5 {; e; N3 H! p l+ Q def evacuate(self, session, host=None, admin_pass=None, force=None):* i. C% H5 X0 f6 {7 w: W- `
body = {"evacuate": {}}
3 f( n- j6 A$ x# c if host is not None:5 }. c: l! m, K E$ Q) a
body["evacuate"]["host"] = host
' @$ w$ N( m6 y. c if admin_pass is not None:" O7 f6 G! R: j- u6 E- n
body["evacuate"]["adminPass"] = admin_pass
7 C5 o2 x7 o$ v' I9 p5 p# } if force is not None:8 W9 g( `1 p5 p( N! n v
body["evacuate"]["force"] = force
' e$ Z+ l8 ^; n self._action(session, body)
' \; a' ~5 c; I# d% |7 v def start(self, session):
7 o/ X) K$ K. k- l7 `0 w e. Z body = {"os-start": None}
' N3 k/ B. \9 V8 c' J7 b! T self._action(session, body)
1 S+ N& C6 T3 N8 |+ `- r( R, F def stop(self, session):/ s+ d. V7 s' o9 d' q {2 y
body = {"os-stop": None}) B8 U5 i3 }4 v0 t- R( U2 Y
self._action(session, body); `& t6 k/ i% {: L$ a/ ^* S
def shelve(self, session):
# j# q' T( G' H! _ body = {"shelve": None}
) ?: T6 _" C! Q ` self._action(session, body)
; S& g1 W6 ]. M6 ?+ B def unshelve(self, session):4 G. v' R [7 k+ I2 j- l
body = {"unshelve": None}
. U+ z$ d' i0 J6 a self._action(session, body)* }7 D1 J0 Z$ W) @9 u
def migrate(self, session):1 \* @7 ~' L7 Q" G
body = {"migrate": None}! l9 B# d6 L6 C; p
self._action(session, body)
! w: \2 h6 x) X" g) T def get_console_output(self, session, length=None):
Q a( z7 b9 V6 R8 [0 C3 }: Z body = {"os-getConsoleOutput": {}}
) T1 j5 w: S* f. G+ | if length is not None:& T' b6 V# P0 A, d9 G2 t& p
body["os-getConsoleOutput"]["length"] = length A7 Y# V7 `% n2 G, B7 J ]1 c
resp = self._action(session, body)# Z( P+ _! _% p. c: I
return resp.json()
; h$ `9 q: _( b def get_console_url(self, session, console_type):
& p4 p N+ E' ?, q* h action = CONSOLE_TYPE_ACTION_MAPPING.get(console_type); {5 f2 b7 l- u4 C' E4 v
if not action: r& j% U+ [) Q$ B4 q
raise ValueError("Unsupported console type %s" % console_type)
1 W1 J2 }) }) \ b1 i0 ? body = {action: {'type': console_type}}4 G8 D; j7 t9 F
resp = self._action(session, body)" ~; x1 y' U* v
return resp.json().get('console')0 L" K) R/ q' f" j( A$ E# l
def live_migrate(self, session, host, force, block_migration,
& s0 G0 X3 X; D$ b& t. M# X disk_over_commit=False):
7 Z# D6 F, G% `; x: o$ r if utils.supports_microversion(session, '2.30'):
. b k! c/ K6 K" y* u: w return self._live_migrate_30(
& n E/ s3 I% S7 _- }% T session, host,' {2 B% k4 t: ^; w9 b
force=force,4 B8 J6 _ z( B' N: ]7 n( @. N0 S
block_migration=block_migration)) k1 S6 q: D2 ^) \+ J# R
elif utils.supports_microversion(session, '2.25'):
& L; W) G6 f# n! ]! U return self._live_migrate_25(
4 Q7 x7 P: r' y session, host,- g% i' ^8 o9 t5 C
force=force,6 q H. T* S' k) l, g& }
block_migration=block_migration); k, T3 l. D7 |) {
else:
5 u+ \( A/ `) L/ b# t* }% ~; g l return self._live_migrate(" ~6 F, W+ p. `2 j0 z: P
session, host, |! w4 d% S" z4 R% o
force=force,
+ D% M8 B' q4 l block_migration=block_migration,
' N- E \+ o0 d7 z$ H( c) } disk_over_commit=disk_over_commit)0 t5 W' G4 u( l+ n) N! ^" }
def _live_migrate_30(self, session, host, force, block_migration):
/ ^% r% b0 ~7 y! ]5 v7 } microversion = '2.30'; d+ @) f& [. z$ l* A2 v: l
body = {'host': None}
( T# X% S+ r" n% z) H if block_migration is None:; E( R2 X% u7 H
block_migration = 'auto'
3 f+ n. @1 ]& }' q. F body['block_migration'] = block_migration* R4 _& B! U2 ]: G! n x
if host:2 B( g6 N: V$ B2 L( Y
body['host'] = host
+ `5 ]1 w3 C+ R8 P2 {) L if force:
' c) z: G @ J: X, n. k* k* y5 r body['force'] = force
( J4 L. x4 i4 i2 c7 p, n+ ~9 d self._action(8 ?. x( ]; g1 O" U8 A
session, {'os-migrateLive': body}, microversion=microversion)
- `' }1 }3 m3 {8 Y% z/ T def _live_migrate_25(self, session, host, force, block_migration):
+ h$ D4 X) z8 N6 ?- O microversion = '2.25'; q E6 V' |0 O+ a
body = {'host': None}
$ s) X2 M; i8 a% [6 E if block_migration is None:- P6 n! c# W2 K; i4 f
block_migration = 'auto'& L+ p% w; B3 @4 _
body['block_migration'] = block_migration; \ r1 s9 ?5 n( h: b2 y
if host:8 ?; b4 a3 ~( y
body['host'] = host
6 B" N6 j; B- N8 I3 G: v$ c* p if not force:
( _, Q C' n. f. w raise ValueError(" M' s' S: Y$ Q/ j
"Live migration on this cloud implies 'force'"' o/ x7 E6 M) ~' P
" if the 'host' option has been given and it is not"
: ?: J5 h9 J# x. I3 f2 A " possible to disable. It is recommended to not use 'host'"
! V5 i' g" ^* d2 ~ " at all on this cloud as it is inherently unsafe, but if"
7 Y- D( O k4 \" k, L1 o: j " it is unavoidable, please supply 'force=True' so that it"3 ?; N- ?; v4 O) _& W. B, l
" is clear you understand the risks.")' A8 \4 C" `0 v) ?- w' q
self._action(7 S5 _/ C$ x0 F5 M% _
session, {'os-migrateLive': body}, microversion=microversion)) n3 e! u( i- P- O
def _live_migrate(self, session, host, force, block_migration,
6 l3 s$ z0 @& h/ u7 m3 f disk_over_commit):
4 }9 c* }( X1 G l microversion = None! w& Q5 e) E8 i& Q) `3 Y' q6 x
body = {2 `! I7 _1 }( D1 t
'host': None,6 ]2 j) W' H5 D3 H
}" r5 K3 G" F" O6 z+ G
if block_migration == 'auto':5 |' H+ z& H# @
raise ValueError(
* ~ s& m6 O3 U/ r' O6 v/ E "Live migration on this cloud does not support 'auto' as"+ S+ W: u5 ]5 K: a* v2 H! E& I& n2 S
" a parameter to block_migration, but only True and False.")/ i1 @$ U& d) ~& {$ {* k! c7 g
body['block_migration'] = block_migration or False9 ~% j4 U# ^$ |- M& P0 Z
body['disk_over_commit'] = disk_over_commit or False
' u$ S4 ~( [4 D& d7 E& G5 @ if host:
6 Q# u( t5 l* `& f% W, e body['host'] = host# q6 o2 i N3 M' @" Q: U
if not force:4 Y" ~' V* |6 |- y2 _
raise ValueError(
+ Z& R3 e* b6 Z1 j "Live migration on this cloud implies 'force'"- ^% N. v: M: n
" if the 'host' option has been given and it is not"6 ]( k3 x& v0 E$ [
" possible to disable. It is recommended to not use 'host'"0 m% X: K. j# U
" at all on this cloud as it is inherently unsafe, but if": I; m. o+ } x. h0 s# N9 Z
" it is unavoidable, please supply 'force=True' so that it"* _; o3 _- O9 K( v: ^
" is clear you understand the risks.")3 Y& _5 ?4 i, V; y5 t
self._action(
; L, V1 w6 F, L7 x$ F3 ^9 Y# Y session, {'os-migrateLive': body}, microversion=microversion)( ]+ P) D7 V) j1 n# z
def fetch_security_groups(self, session):
$ ]8 o( o0 a$ {+ A& F3 U """Fetch security groups of a server. a, j2 h$ o" e2 x1 m/ z$ H8 f
:returns: Updated Server instance.4 h$ n! C/ \6 w; [7 J' A
"""5 m. x7 E0 s# \' ^. Y2 y
url = utils.urljoin(Server.base_path, self.id, 'os-security-groups')7 t0 X: ^4 I# F7 h A
response = session.get(url)3 q+ L; p& ^; @" b' g. P
exceptions.raise_from_response(response)
1 c& S" k: U/ @, { try:, a3 D. V) s- m) I# _, l' I; o/ H/ H
data = response.json()
8 ?, z7 _5 K. B9 y if 'security_groups' in data:
" {+ w+ i! v% G, M self.security_groups = data['security_groups']6 X# ]# y8 `: f& ^* b
except ValueError:& E8 g* j' `2 q! B5 B
pass& B/ d: L- b: G3 Z9 i
return self2 N! Q, Q; f4 o. k7 T" |+ R
! Y3 O4 e6 v# b7 OServerDetail = Server
2 K, z B& V* n2 U9 k
; K( w" X- ^1 n x' E& ~$ _, y% R( ~5 E1 C! ~0 d4 d8 I( e
|
|