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