Skip to content

Commit 1c24203

Browse files
Wayne OkumaLakshmi N SampathttripppkoniszewskiMichal Jastrzebski
committedAug 28, 2014
Glance Metadata Definitions Catalog - Seed
Implements: blueprint metadata-schema-catalog A common API hosted by the Glance service for vendors, admins, services, and users to meaningfully define available key / value pair and tag metadata. The intent is to enable better metadata collaboration across artifacts, services, and projects for OpenStack users. This is about the definition of the available metadata that can be used on different types of resources (images, artifacts, volumes, flavors, aggregates, etc). A definition includes the properties type, its key, it's description, and it's constraints. This catalogue will not store the values for specific instance properties. Change-Id: Ib1c1abf80879fb6dcd5ee30c7d2bc65b0ba720d5 DocImpact Co-Authored-By: Lakshmi N Sampath <[email protected]> Co-Authored-By: Wayne Okuma <[email protected]> Co-Authored-By: Travis Tripp <[email protected]> Co-Authored-By: Pawel Koniszewski <[email protected]> Co-Authored-By: Michal Jastrzebski <[email protected]> Co-Authored-By: Michal Dulko <[email protected]>
1 parent 7454aac commit 1c24203

17 files changed

+1140
-1
lines changed
 

‎README.rst

-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,5 @@ Glance
55
Glance is a project that defines services for discovering, registering,
66
retrieving and storing virtual machine images. Use the following resources
77
to learn more:
8-
98
* `Official Glance documentation <http://docs.openstack.org/developer/glance/>`_
109
* `Official Client documentation <http://docs.openstack.org/developer/python-glanceclient/>`_

‎etc/metadefs/README

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
This directory contains predefined namespaces for Glance Metadata Definitions
2+
Catalog. Files from this directory can be loaded into the database using
3+
db_load_metadefs command for glance-manage. Similarly you can unload the
4+
definitions using db_unload_metadefs command.
+181
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
{
2+
"namespace": "OS::Compute::HostCapabilities",
3+
"display_name": "Compute Host Capabilities",
4+
"description": "Capabilities provided by the Compute Host. This provides the ability to fine tune the harware specification required when a new vm is requested.",
5+
"visibility": "public",
6+
"protected": true,
7+
"resource_type_associations": [
8+
{
9+
"name": "OS::Nova::Flavor",
10+
"prefix": "capabilities:"
11+
},
12+
{
13+
"name": "OS::Nova::Aggregate",
14+
"prefix": "aggregate_instance_extra_spec:"
15+
}
16+
],
17+
"properties": {
18+
"cpu_info:vendor": {
19+
"title": "Vendor",
20+
"description": "Specifies the CPU manufacturer.",
21+
"type": "string",
22+
"enum": [
23+
"Intel",
24+
"AMD"
25+
]
26+
},
27+
"cpu_info:model": {
28+
"title": "Model",
29+
"description": "Specifies the CPU model. Use this property to ensure that your vm runs on a a specific cpu model.",
30+
"type": "string",
31+
"enum": [
32+
"Conroe",
33+
"Core2Duo",
34+
"Penryn",
35+
"Nehalem",
36+
"Westmere",
37+
"SandyBridge",
38+
"IvyBridge",
39+
"Haswell",
40+
"Broadwell",
41+
"Delhi",
42+
"Seoul",
43+
"Abu Dhabi",
44+
"Interlagos",
45+
"Kabini",
46+
"Valencia",
47+
"Zurich",
48+
"Budapest",
49+
"Barcelona",
50+
"Suzuka",
51+
"Shanghai",
52+
"Istanbul",
53+
"Lisbon",
54+
"Magny-Cours",
55+
"Valencia",
56+
"Cortex-A57",
57+
"Cortex-A53",
58+
"Cortex-A12",
59+
"Cortex-A17",
60+
"Cortex-A15",
61+
"Coretx-A7",
62+
"X-Gene"
63+
]
64+
},
65+
"cpu_info:arch": {
66+
"title": "Architecture",
67+
"description": "Specifies the CPU architecture. Use this property to specify the architecture supported by the hypervisor.",
68+
"type": "string",
69+
"enum": [
70+
"x86",
71+
"x86_64",
72+
"i686",
73+
"ia64",
74+
"ARMv8-A",
75+
"ARMv7-A"
76+
]
77+
},
78+
"cpu_info:topology:cores": {
79+
"title": "cores",
80+
"description": "Number of cores.",
81+
"type": "integer",
82+
"readonly": false,
83+
"default": 1
84+
},
85+
"cpu_info:topology:threads": {
86+
"title": "threads",
87+
"description": "Number of threads.",
88+
"type": "integer",
89+
"readonly": false,
90+
"default": 1
91+
},
92+
"cpu_info:topology:sockets": {
93+
"title": "sockets",
94+
"description": "Number of sockets.",
95+
"type": "integer",
96+
"readonly": false,
97+
"default": 1
98+
},
99+
"cpu_info:features": {
100+
"title": "Features",
101+
"description": "Specifies CPU flags/features. Using this property you can specify the required set of instructions supported by a vm.",
102+
"type": "array",
103+
"items": {
104+
"type": "string",
105+
"enum": [
106+
"aes",
107+
"vme",
108+
"de",
109+
"pse",
110+
"tsc",
111+
"msr",
112+
"pae",
113+
"mce",
114+
"cx8",
115+
"apic",
116+
"sep",
117+
"mtrr",
118+
"pge",
119+
"mca",
120+
"cmov",
121+
"pat",
122+
"pse36",
123+
"clflush",
124+
"dts",
125+
"acpi",
126+
"mmx",
127+
"fxsr",
128+
"sse",
129+
"sse2",
130+
"ss",
131+
"ht",
132+
"tm",
133+
"ia64",
134+
"pbe",
135+
"rdtscp",
136+
"pni",
137+
"pclmulqdq",
138+
"dtes64",
139+
"monitor",
140+
"ds_cpl",
141+
"vmx",
142+
"smx",
143+
"est",
144+
"tm2",
145+
"ssse3",
146+
"cid",
147+
"fma",
148+
"cx16",
149+
"xtpr",
150+
"pdcm",
151+
"pcid",
152+
"dca",
153+
"sse4_1",
154+
"sse4_2",
155+
"x2apic",
156+
"movbe",
157+
"popcnt",
158+
"tsc_deadline_timer",
159+
"xsave",
160+
"avx",
161+
"f16c",
162+
"rdrand",
163+
"fsgsbase",
164+
"bmi1",
165+
"hle",
166+
"avx2",
167+
"smep",
168+
"bmi2",
169+
"erms",
170+
"invpcid",
171+
"rtm",
172+
"mpx",
173+
"rdseed",
174+
"adx",
175+
"smap"
176+
]
177+
}
178+
}
179+
},
180+
"objects": []
181+
}

‎etc/metadefs/compute-hypervisor.json

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"namespace": "OS::Compute::Hypervisor",
3+
"display_name": "Hypervisor Selection",
4+
"description": "Choose capabilities that should be provided by the Compute Host. This provides the ability to fine tune the harware specification required when a new vm is requested.",
5+
"visibility": "public",
6+
"protected": true,
7+
"resource_type_associations": [
8+
{
9+
"name": "OS::Glance::Image"
10+
}
11+
],
12+
"properties": {
13+
"hypervisor_type": {
14+
"title": "Hypervisor Type",
15+
"description": "The hypervisor type. It may be used by the host properties filter for scheduling. The ImagePropertiesFilter filters compute nodes that satisfy any architecture, hypervisor type, or virtual machine mode properties specified on the instance's image properties. Image properties are contained in the image dictionary in the request_spec.",
16+
"type": "string",
17+
"enum": [
18+
"xen",
19+
"qemu",
20+
"kvm",
21+
"lxc",
22+
"uml",
23+
"vmware",
24+
"hyperv"
25+
]
26+
},
27+
"vm_mode": {
28+
"title": "VM Mode",
29+
"description": "The virtual machine mode. This represents the host/guest ABI (application binary interface) used for the virtual machine. It may be used by the host properties filter for scheduling. \n\n hvm — Fully virtualized - This is the virtual machine mode (vm_mode) used by QEMU and KVM. \n\n xen - Xen 3.0 paravirtualized. \n\n uml — User Mode Linux paravirtualized. \n\n exe — Executables in containers. This is the mode used by LXC.",
30+
"type": "string",
31+
"enum": [
32+
"hvm",
33+
"xen",
34+
"uml",
35+
"exe"
36+
]
37+
}
38+
},
39+
"objects": []
40+
}

‎etc/metadefs/compute-libvirt.json

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
{
2+
"namespace": "OS::Compute::Libvirt",
3+
"display_name": "libvirt Driver Options",
4+
"description": "The libvirt compute driver options. \n\nThese are properties specific to compute drivers. For a list of all hypervisors, see here: https://wiki.openstack.org/wiki/HypervisorSupportMatrix.",
5+
"visibility": "public",
6+
"protected": true,
7+
"resource_type_associations": [
8+
{
9+
"name": "OS::Glance::Image"
10+
}
11+
],
12+
"properties": {
13+
"hw_disk_bus": {
14+
"title": "Disk Bus",
15+
"description": "Specifies the type of disk controller to attach disk devices to.",
16+
"type": "string",
17+
"enum": [
18+
"scsi",
19+
"virtio",
20+
"uml",
21+
"xen",
22+
"ide",
23+
"usb"
24+
]
25+
},
26+
"hw_rng_model": {
27+
"title": "Random Number Generator Device",
28+
"description": "Adds a random-number generator device to the image's instances. The cloud administrator can enable and control device behavior by configuring the instance's flavor. By default: The generator device is disabled. /dev/random is used as the default entropy source. To specify a physical HW RNG device, use the following option in the nova.conf file: rng_dev_path=/dev/hwrng",
29+
"type": "string",
30+
"default": "virtio"
31+
},
32+
"hw_machine_type": {
33+
"title": "Machine Type",
34+
"description": "Enables booting an ARM system using the specified machine type. By default, if an ARM image is used and its type is not specified, Compute uses vexpress-a15 (for ARMv7) or virt (for AArch64) machine types. Valid types can be viewed by using the virsh capabilities command (machine types are displayed in the machine tag).",
35+
"type": "string"
36+
},
37+
"hw_scsi_model": {
38+
"title": "SCSI Model",
39+
"description": "Enables the use of VirtIO SCSI (virtio-scsi) to provide block device access for compute instances; by default, instances use VirtIO Block (virtio-blk). VirtIO SCSI is a para-virtualized SCSI controller device that provides improved scalability and performance, and supports advanced SCSI hardware.",
40+
"type": "string",
41+
"default": "virtio-scsi"
42+
},
43+
"hw_video_model": {
44+
"title": "Video Model",
45+
"description": "The video image driver used.",
46+
"type": "string",
47+
"enum": [
48+
"vga",
49+
"cirrus",
50+
"vmvga",
51+
"xen",
52+
"qxl"
53+
]
54+
},
55+
"hw_video_ram": {
56+
"title": "Max Video Ram",
57+
"description": "Maximum RAM for the video image. Used only if a hw_video:ram_max_mb value has been set in the flavor's extra_specs and that value is higher than the value set in hw_video_ram.",
58+
"type": "integer"
59+
},
60+
"os_command_line": {
61+
"title": "Kernel Command Line",
62+
"description": "The kernel command line to be used by the libvirt driver, instead of the default. For linux containers (LXC), the value is used as arguments for initialization. This key is valid only for Amazon kernel, ramdisk, or machine images (aki, ari, or ami).",
63+
"type": "string"
64+
},
65+
"hw_vif_model": {
66+
"title": "Virtual Network Interface",
67+
"description": "Specifies the model of virtual network interface device to use. The valid options depend on the configured hypervisor. KVM and QEMU: e1000, ne2k_pci, pcnet, rtl8139, and virtio. VMware: e1000, e1000e, VirtualE1000, VirtualE1000e, VirtualPCNet32, VirtualSriovEthernetCard, and VirtualVmxnet. Xen: e1000, netfront, ne2k_pci, pcnet, and rtl8139.",
68+
"type": "string",
69+
"enum": [
70+
"e1000",
71+
"ne2k_pci",
72+
"pcnet",
73+
"rtl8139",
74+
"virtio",
75+
"e1000",
76+
"e1000e",
77+
"VirtualE1000",
78+
"VirtualE1000e",
79+
"VirtualPCNet32",
80+
"VirtualSriovEthernetCard",
81+
"VirtualVmxnet",
82+
"netfront",
83+
"ne2k_pci"
84+
]
85+
},
86+
"hw_qemu_guest_agent": {
87+
"title": "QEMU Guest Agent",
88+
"description": "It is a daemon program running inside the domain which is supposed to help management applications with executing functions which need assistance of the guest OS. For example, freezing and thawing filesystems, entering suspend. However, guest agent (GA) is not bullet proof, and hostile guest OS can send spurious replies.",
89+
"type": "string",
90+
"enum": ["yes", "no"]
91+
}
92+
},
93+
"objects": []
94+
}

‎etc/metadefs/compute-quota.json

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
{
2+
"namespace": "OS::Compute::Quota",
3+
"display_name": "Flavor Quota",
4+
"description": "Compute drivers may enable quotas on CPUs available to a VM, disk tuning, bandwidth I/O, and instance VIF traffic control. See: http://docs.openstack.org/admin-guide-cloud/content/customize-flavors.html",
5+
"visibility": "public",
6+
"protected": true,
7+
"resource_type_associations": [
8+
{
9+
"name": "OS::Nova::Flavor"
10+
}
11+
],
12+
"objects": [
13+
{
14+
"name": "CPU Limits",
15+
"description": "You can configure the CPU limits with control parameters.",
16+
"properties": {
17+
"quota:cpu_shares": {
18+
"title": "Quota: CPU Shares",
19+
"description": "Specifies the proportional weighted share for the domain. If this element is omitted, the service defaults to the OS provided defaults. There is no unit for the value; it is a relative measure based on the setting of other VMs. For example, a VM configured with value 2048 gets twice as much CPU time as a VM configured with value 1024.",
20+
"type": "integer"
21+
},
22+
"quota:cpu_period": {
23+
"title": "Quota: CPU Period",
24+
"description": "Specifies the enforcement interval (unit: microseconds) for QEMU and LXC hypervisors. Within a period, each VCPU of the domain is not allowed to consume more than the quota worth of runtime. The value should be in range [1000, 1000000]. A period with value 0 means no value.",
25+
"type": "integer",
26+
"minimum": 1000,
27+
"maximum": 1000000
28+
},
29+
"quota:cpu_quota": {
30+
"title": "Quota: CPU Quota",
31+
"description": "Specifies the maximum allowed bandwidth (unit: microseconds). A domain with a negative-value quota indicates that the domain has infinite bandwidth, which means that it is not bandwidth controlled. The value should be in range [1000, 18446744073709551] or less than 0. A quota with value 0 means no value. You can use this feature to ensure that all vCPUs run at the same speed.",
32+
"type": "integer"
33+
}
34+
}
35+
},
36+
{
37+
"name": "Disk QoS",
38+
"description": "Using disk I/O quotas, you can set maximum disk write to 10 MB per second for a VM user.",
39+
"properties": {
40+
"quota:disk_read_bytes_sec": {
41+
"title": "Quota: Disk read bytes / sec",
42+
"description": "Sets disk I/O quota for disk read bytes / sec.",
43+
"type": "integer"
44+
},
45+
"quota:disk_read_iops_sec": {
46+
"title": "Quota: Disk read IOPS / sec",
47+
"description": "Sets disk I/O quota for disk read IOPS / sec.",
48+
"type": "integer"
49+
},
50+
"quota:disk_write_bytes_sec": {
51+
"title": "Quota: Disk Write Bytes / sec",
52+
"description": "Sets disk I/O quota for disk write bytes / sec.",
53+
"type": "integer"
54+
},
55+
"quota:disk_write_iops_sec": {
56+
"title": "Quota: Disk Write IOPS / sec",
57+
"description": "Sets disk I/O quota for disk write IOPS / sec.",
58+
"type": "integer"
59+
},
60+
"quota:disk_total_bytes_sec": {
61+
"title": "Quota: Disk Total Bytes / sec",
62+
"description": "Sets disk I/O quota for total disk bytes / sec.",
63+
"type": "integer"
64+
},
65+
"quota:disk_total_iops_sec": {
66+
"title": "Quota: Disk Total IOPS / sec",
67+
"description": "Sets disk I/O quota for disk total IOPS / sec.",
68+
"type": "integer"
69+
}
70+
}
71+
},
72+
{
73+
"name": "Virtual Interface QoS",
74+
"description": "Bandwidth QoS tuning for instance virtual interfaces (VIFs) may be specified with these properties. Incoming and outgoing traffic can be shaped independently. If not specified, no quality of service (QoS) is applied on that traffic direction. So, if you want to shape only the network's incoming traffic, use inbound only (and vice versa). The OpenStack Networking service abstracts the physical implementation of the network, allowing plugins to configure and manage physical resources. Virtual Interfaces (VIF) in the logical model are analogous to physical network interface cards (NICs). VIFs are typically owned a managed by an external service; for instance when OpenStack Networking is used for building OpenStack networks, VIFs would be created, owned, and managed in Nova. VIFs are connected to OpenStack Networking networks via ports. A port is analogous to a port on a network switch, and it has an administrative state. When a VIF is attached to a port the OpenStack Networking API creates an attachment object, which specifies the fact that a VIF with a given identifier is plugged into the port.",
75+
"properties": {
76+
"quota:vif_inbound_average": {
77+
"title": "Quota: VIF Inbound Average",
78+
"description": "Network Virtual Interface (VIF) inbound average in kilobytes per second. Specifies average bit rate on the interface being shaped.",
79+
"type": "integer"
80+
},
81+
"quota:vif_inbound_burst": {
82+
"title": "Quota: VIF Inbound Burst",
83+
"description": "Network Virtual Interface (VIF) inbound burst in total kilobytes. Specifies the amount of bytes that can be burst at peak speed.",
84+
"type": "integer"
85+
},
86+
"quota:vif_inbound_peak": {
87+
"title": "Quota: VIF Inbound Peak",
88+
"description": "Network Virtual Interface (VIF) inbound peak in kilobytes per second. Specifies maximum rate at which an interface can receive data.",
89+
"type": "integer"
90+
},
91+
"quota:vif_outbound_average": {
92+
"title": "Quota: VIF Outbound Average",
93+
"description": "Network Virtual Interface (VIF) outbound average in kilobytes per second. Specifies average bit rate on the interface being shaped.",
94+
"type": "integer"
95+
},
96+
"quota:vif_outbound_burst": {
97+
"title": "Quota: VIF Outbound Burst",
98+
"description": "Network Virtual Interface (VIF) outbound burst in total kilobytes. Specifies the amount of bytes that can be burst at peak speed.",
99+
"type": "integer"
100+
},
101+
"quota:vif_outbound_peak": {
102+
"title": "Quota: VIF Outbound Burst",
103+
"description": "Network Virtual Interface (VIF) outbound peak in kilobytes per second. Specifies maximum rate at which an interface can send data.",
104+
"type": "integer"
105+
}
106+
}
107+
}
108+
]
109+
}

‎etc/metadefs/compute-randomgen.json

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"namespace": "OS::Compute::RandomNumberGenerator",
3+
"display_name": "Random Number Generator",
4+
"description": "If a random-number generator device has been added to the instance through its image properties, the device can be enabled and configured.",
5+
"visibility": "public",
6+
"protected": true,
7+
"resource_type_associations": [
8+
{
9+
"name": "OS::Nova::Flavor"
10+
}
11+
],
12+
"properties": {
13+
"hw_rng:allowed": {
14+
"title": "Random Number Generator Allowed",
15+
"description": "",
16+
"type": "boolean"
17+
},
18+
"hw_rng:rate_bytes": {
19+
"title": "Random number generator limits.",
20+
"description": "Allowed amount of bytes that the guest can read from the host's entropy per period.",
21+
"type": "integer"
22+
},
23+
"hw_rng:rate_period": {
24+
"title": "Random number generator read period.",
25+
"description": "Duration of the read period in seconds.",
26+
"type": "integer"
27+
}
28+
}
29+
}

‎etc/metadefs/compute-trust.json

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"namespace": "OS::Compute::Trust",
3+
"display_name": "Trusted Compute Pools (Intel® TXT)",
4+
"description": "Trusted compute pools with Intel® Trusted Execution Technology (Intel® TXT) support IT compliance by protecting virtualized data centers - private, public, and hybrid clouds against attacks toward hypervisor and BIOS, firmware, and other pre-launch software components.",
5+
"visibility": "public",
6+
"protected": true,
7+
"resource_type_associations": [
8+
{
9+
"name": "OS::Nova::Flavor"
10+
}
11+
],
12+
"properties": {
13+
"trust:trusted_host": {
14+
"title": "Intel® TXT attestation",
15+
"description": "Select to ensure that node has been attested by Intel® Trusted Execution Technology (Intel® TXT).",
16+
"type": "boolean"
17+
}
18+
}
19+
}
+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"namespace": "OS::Compute::VirtCPUTopology",
3+
"display_name": "Virtual CPU Topology",
4+
"description": "This provides the preferred socket/core/thread counts for the virtual CPU instance exposed to guests. This enables the ability to avoid hitting limitations on vCPU topologies that OS vendors place on their products. See also: http://git.openstack.org/cgit/openstack/nova-specs/tree/specs/juno/virt-driver-vcpu-topology.rst",
5+
"visibility": "public",
6+
"protected": true,
7+
"resource_type_associations": [
8+
{
9+
"name": "OS::Glance::Image",
10+
"prefix": "hw_"
11+
},
12+
{
13+
"name": "OS::Cinder::Volume",
14+
"prefix": "hw_",
15+
"properties_target": "image"
16+
},
17+
{
18+
"name": "OS::Nova::Flavor",
19+
"prefix": "hw:"
20+
}
21+
],
22+
"properties": {
23+
"cpu_sockets": {
24+
"title": "vCPU Sockets",
25+
"description": "Preferred number of sockets to expose to the guest.",
26+
"type": "integer"
27+
},
28+
"cpu_cores": {
29+
"title": "vCPU Cores",
30+
"description": "Preferred number of cores to expose to the guest.",
31+
"type": "integer"
32+
},
33+
"cpu_threads": {
34+
"title": " vCPU Threads",
35+
"description": "Preferred number of threads to expose to the guest.",
36+
"type": "integer"
37+
},
38+
"cpu_maxsockets": {
39+
"title": "Max vCPU Sockets",
40+
"description": "Maximum number of sockets to expose to the guest.",
41+
"type": "integer"
42+
},
43+
"cpu_maxcores": {
44+
"title": "Max vCPU Cores",
45+
"description": "Maximum number of cores to expose to the guest.",
46+
"type": "integer"
47+
},
48+
"cpu_maxthreads": {
49+
"title": "Max vCPU Threads",
50+
"description": "Maximum number of threads to expose to the guest.",
51+
"type": "integer"
52+
}
53+
}
54+
}

‎etc/metadefs/compute-vmware.json

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"namespace": "OS::Compute::VMwAre",
3+
"display_name": "VMware Driver Options",
4+
"description": "The VMware compute driver options. \n\nThese are properties specific to compute drivers. For a list of all hypervisors, see here: https://wiki.openstack.org/wiki/HypervisorSupportMatrix.",
5+
"visibility": "public",
6+
"protected": true,
7+
"resource_type_associations": [
8+
{
9+
"name": "OS::Glance::Image"
10+
}
11+
],
12+
"properties": {
13+
"vmware_adaptertype": {
14+
"title": "Disk Adapter Type",
15+
"description": "The virtual SCSI or IDE controller used by the hypervisor.",
16+
"type": "string",
17+
"enum": [
18+
"lsiLogic",
19+
"busLogic",
20+
"ide"
21+
]
22+
},
23+
"vmware_ostype": {
24+
"title": "OS Type",
25+
"description": "A VMware GuestID which describes the operating system installed in the image. This value is passed to the hypervisor when creating a virtual machine. If not specified, the key defaults to otherGuest. See thinkvirt.com.",
26+
"type": "string",
27+
"default": "otherGuest"
28+
}
29+
},
30+
"objects": []
31+
}

‎etc/metadefs/compute-watchdog.json

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"namespace": "OS::Compute::Watchdog",
3+
"display_name": "Watchdog Behavior",
4+
"description": "Compute drivers may enable watchdog behavior over instances. See: http://docs.openstack.org/admin-guide-cloud/content/customize-flavors.html",
5+
"visibility": "public",
6+
"protected": true,
7+
"resource_type_associations": [
8+
{
9+
"name": "OS::Glance::Image"
10+
},
11+
{
12+
"name": "OS::Cinder::Volume",
13+
"properties_target": "image"
14+
},
15+
{
16+
"name": "OS::Nova::Flavor"
17+
}
18+
],
19+
"properties": {
20+
"hw_watchdog_action": {
21+
"title": "Watchdog Action",
22+
"description": "For the libvirt driver, you can enable and set the behavior of a virtual hardware watchdog device for each flavor. Watchdog devices keep an eye on the guest server, and carry out the configured action, if the server hangs. The watchdog uses the i6300esb device (emulating a PCI Intel 6300ESB). If hw_watchdog_action is not specified, the watchdog is disabled. Watchdog behavior set using a specific image's properties will override behavior set using flavors.",
23+
"type": "string",
24+
"enum": [
25+
"disabled",
26+
"reset",
27+
"poweroff",
28+
"pause",
29+
"none"
30+
]
31+
}
32+
}
33+
}

‎etc/metadefs/compute-xenapi.json

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"namespace": "OS::Compute::XenAPI",
3+
"display_name": "XenAPI Driver Options",
4+
"description": "The XenAPI compute driver options. \n\nThese are properties specific to compute drivers. For a list of all hypervisors, see here: https://wiki.openstack.org/wiki/HypervisorSupportMatrix.",
5+
"visibility": "public",
6+
"protected": true,
7+
"resource_type_associations": [
8+
{
9+
"name": "OS::Glance::Image"
10+
}
11+
],
12+
"properties": {
13+
"os_type": {
14+
"title": "OS Type",
15+
"description": "The operating system installed on the image. The XenAPI driver contains logic that takes different actions depending on the value of the os_type parameter of the image. For example, for os_type=windows images, it creates a FAT32-based swap partition instead of a Linux swap partition, and it limits the injected host name to less than 16 characters.",
16+
"type": "string",
17+
"enum": [
18+
"linux",
19+
"windows"
20+
]
21+
},
22+
"auto_disk_config": {
23+
"title": "Disk Adapter Type",
24+
"description": "If true, the root partition on the disk is automatically resized before the instance boots. This value is only taken into account by the Compute service when using a Xen-based hypervisor with the XenAPI driver. The Compute service will only attempt to resize if there is a single partition on the image, and only if the partition is in ext3 or ext4 format.",
25+
"type": "boolean"
26+
}
27+
},
28+
"objects": []
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"display_name": "Common Image Properties",
3+
"namespace": "OS::Glance:CommonImageProperties",
4+
"description": "When adding an image to Glance, you may specify some common image properties that may prove useful to consumers of your image.",
5+
"protected": true,
6+
"resource_type_associations" : [
7+
],
8+
"properties": {
9+
"kernel_id": {
10+
"title": "Kernel ID",
11+
"type": "string",
12+
"pattern": "^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}$",
13+
"description": "ID of image stored in Glance that should be used as the kernel when booting an AMI-style image."
14+
},
15+
"ramdisk_id": {
16+
"title": "Ramdisk ID",
17+
"type": "string",
18+
"pattern": "^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}$",
19+
"description": "ID of image stored in Glance that should be used as the ramdisk when booting an AMI-style image."
20+
},
21+
"instance_uuid": {
22+
"title": "Instance ID",
23+
"type": "string",
24+
"description": "ID of instance used to create this image."
25+
},
26+
"architecture": {
27+
"title": "CPU Architecture",
28+
"description": "The CPU architecture that must be supported by the hypervisor. For example, x86_64, arm, or ppc64. Run uname -m to get the architecture of a machine. We strongly recommend using the architecture data vocabulary defined by the libosinfo project for this purpose.",
29+
"type": "string"
30+
},
31+
"os_distro": {
32+
"title": "OS Distro",
33+
"description": "The common name of the operating system distribution in lowercase (uses the same data vocabulary as the libosinfo project). Specify only a recognized value for this field. Deprecated values are listed to assist you in searching for the recognized value.",
34+
"type": "string"
35+
},
36+
"os_version": {
37+
"title": "OS Version",
38+
"description": "Operating system version as specified by the distributor. (for example, '11.10')",
39+
"type": "string"
40+
}
41+
}
42+
}

‎glance/cmd/manage.py

+43
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
from glance.common import utils
4747
from glance.db import migration as db_migration
4848
from glance.db.sqlalchemy import api as db_api
49+
from glance.db.sqlalchemy import metadata
4950
from glance.openstack.common import gettextutils
5051
from glance.openstack.common import log
5152
from glance.openstack.common import strutils
@@ -138,6 +139,25 @@ def sync(self, version=None, current_version=None):
138139
version,
139140
sanity_check=self._need_sanity_check())
140141

142+
@args('--path', metavar='<path>', help='Path to the directory where '
143+
'json metadata files are stored')
144+
def load_metadefs(self, path=None):
145+
"""Load metadefinition json files to database"""
146+
metadata.db_load_metadefs(db_api.get_engine(),
147+
path)
148+
149+
def unload_metadefs(self):
150+
"""Unload metadefinitions from database"""
151+
metadata.db_unload_metadefs(db_api.get_engine())
152+
153+
@args('--path', metavar='<path>', help='Path to the directory where '
154+
'json metadata files should be '
155+
'saved.')
156+
def export_metadefs(self, path=None):
157+
"""Export metadefinitions data from database to files"""
158+
metadata.db_export_metadefs(db_api.get_engine(),
159+
path)
160+
141161

142162
class DbLegacyCommands(object):
143163
"""Class for managing the db using legacy commands"""
@@ -161,6 +181,15 @@ def sync(self, version=None, current_version=None):
161181
self.command_object.sync(CONF.command.version,
162182
CONF.command.current_version)
163183

184+
def load_metadefs(self, path=None):
185+
self.command_object.load_metadefs(CONF.command.path)
186+
187+
def unload_metadefs(self):
188+
self.command_object.unload_metadefs()
189+
190+
def export_metadefs(self, path=None):
191+
self.command_object.export_metadefs(CONF.command.path)
192+
164193

165194
def add_legacy_command_parsers(command_object, subparsers):
166195

@@ -191,6 +220,20 @@ def add_legacy_command_parsers(command_object, subparsers):
191220
parser.add_argument('current_version', nargs='?')
192221
parser.set_defaults(action='db_sync')
193222

223+
parser = subparsers.add_parser('db_load_metadefs')
224+
parser.set_defaults(action_fn=legacy_command_object.load_metadefs)
225+
parser.add_argument('path', nargs='?')
226+
parser.set_defaults(action='db_load_metadefs')
227+
228+
parser = subparsers.add_parser('db_unload_metadefs')
229+
parser.set_defaults(action_fn=legacy_command_object.unload_metadefs)
230+
parser.set_defaults(action='db_unload_metadefs')
231+
232+
parser = subparsers.add_parser('db_export_metadefs')
233+
parser.set_defaults(action_fn=legacy_command_object.export_metadefs)
234+
parser.add_argument('path', nargs='?')
235+
parser.set_defaults(action='db_export_metadefs')
236+
194237

195238
def add_command_parsers(subparsers):
196239
command_object = DbCommands()

‎glance/db/metadata.py

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Copyright 2010 United States Government as represented by the
2+
# Administrator of the National Aeronautics and Space Administration.
3+
# All Rights Reserved.
4+
#
5+
# Copyright 2013 OpenStack Foundation
6+
# Copyright 2013 Intel Corporation
7+
#
8+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
9+
# not use this file except in compliance with the License. You may obtain
10+
# a copy of the License at
11+
#
12+
# http://www.apache.org/licenses/LICENSE-2.0
13+
#
14+
# Unless required by applicable law or agreed to in writing, software
15+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17+
# License for the specific language governing permissions and limitations
18+
# under the License.
19+
20+
"""Metadata setup commands."""
21+
22+
from glance.common import utils
23+
from glance.db.sqlalchemy import api as db_api
24+
25+
IMPL = utils.LazyPluggable(
26+
'backend',
27+
config_group='database',
28+
sqlalchemy='glance.db.sqlalchemy.metadata')
29+
30+
31+
def load_metadefs():
32+
"""Read metadefinition files and insert data into the database"""
33+
return IMPL.db_load_metadefs(engine=db_api.get_engine(),
34+
metadata_path=None)
35+
36+
37+
def unload_metadefs():
38+
"""Unload metadefinitions from database"""
39+
return IMPL.db_unload_metadefs(engine=db_api.get_engine())
40+
41+
42+
def export_metadefs():
43+
"""Export metadefinitions from database to files"""
44+
return IMPL.db_export_metadefs(engine=db_api.get_engine(),
45+
metadata_path=None)

‎glance/db/sqlalchemy/metadata.py

+314
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
# Copyright 2010 United States Government as represented by the
2+
# Administrator of the National Aeronautics and Space Administration.
3+
# All Rights Reserved.
4+
#
5+
# Copyright 2013 OpenStack Foundation
6+
# Copyright 2013 Intel Corporation
7+
#
8+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
9+
# not use this file except in compliance with the License. You may obtain
10+
# a copy of the License at
11+
#
12+
# http://www.apache.org/licenses/LICENSE-2.0
13+
#
14+
# Unless required by applicable law or agreed to in writing, software
15+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17+
# License for the specific language governing permissions and limitations
18+
# under the License.
19+
20+
import json
21+
import os
22+
from os.path import isfile
23+
from os.path import join
24+
import re
25+
26+
from oslo.config import cfg
27+
import sqlalchemy
28+
from sqlalchemy.schema import MetaData
29+
30+
from glance.common import utils
31+
from glance import i18n
32+
import glance.openstack.common.log as logging
33+
from glance.openstack.common import timeutils
34+
35+
LOG = logging.getLogger(__name__)
36+
_LE = i18n._LE
37+
_LW = i18n._LW
38+
_LI = i18n._LI
39+
40+
metadata_opts = [
41+
cfg.StrOpt('metadata_source_path', default='/etc/glance/metadefs/',
42+
help=_('Path to the directory where json metadata '
43+
'files are stored'))
44+
]
45+
46+
CONF = cfg.CONF
47+
CONF.register_opts(metadata_opts)
48+
49+
50+
def get_metadef_namespaces_table(meta):
51+
return sqlalchemy.Table('metadef_namespaces', meta, autoload=True)
52+
53+
54+
def get_metadef_resource_types_table(meta):
55+
return sqlalchemy.Table('metadef_resource_types', meta, autoload=True)
56+
57+
58+
def get_metadef_namespace_resource_types_table(meta):
59+
return sqlalchemy.Table('metadef_namespace_resource_types', meta,
60+
autoload=True)
61+
62+
63+
def get_metadef_properties_table(meta):
64+
return sqlalchemy.Table('metadef_properties', meta, autoload=True)
65+
66+
67+
def get_metadef_objects_table(meta):
68+
return sqlalchemy.Table('metadef_objects', meta, autoload=True)
69+
70+
71+
def _get_resource_type_id(meta, name):
72+
resource_types_table = get_metadef_resource_types_table(meta)
73+
return resource_types_table.select().\
74+
where(resource_types_table.c.name == name).execute().fetchone().id
75+
76+
77+
def _get_resource_type(meta, resource_type_id):
78+
resource_types_table = get_metadef_resource_types_table(meta)
79+
return resource_types_table.select().\
80+
where(resource_types_table.c.id == resource_type_id).\
81+
execute().fetchone()
82+
83+
84+
def _get_namespace_resource_types(meta, namespace_id):
85+
namespace_resource_types_table =\
86+
get_metadef_namespace_resource_types_table(meta)
87+
return namespace_resource_types_table.select().\
88+
where(namespace_resource_types_table.c.namespace_id == namespace_id).\
89+
execute().fetchall()
90+
91+
92+
def _get_properties(meta, namespace_id):
93+
properties_table = get_metadef_properties_table(meta)
94+
return properties_table.select().\
95+
where(properties_table.c.namespace_id == namespace_id).\
96+
execute().fetchall()
97+
98+
99+
def _get_objects(meta, namespace_id):
100+
objects_table = get_metadef_objects_table(meta)
101+
return objects_table.select().\
102+
where(objects_table.c.namespace_id == namespace_id).\
103+
execute().fetchall()
104+
105+
106+
def _populate_metadata(meta, metadata_path=None):
107+
if not metadata_path:
108+
metadata_path = CONF.metadata_source_path
109+
110+
try:
111+
json_schema_files = [f for f in os.listdir(metadata_path)
112+
if isfile(join(metadata_path, f))
113+
and f.endswith('.json')]
114+
except OSError as e:
115+
LOG.error(utils.exception_to_str(e))
116+
return
117+
118+
metadef_namespaces_table = get_metadef_namespaces_table(meta)
119+
metadef_namespace_resource_types_tables =\
120+
get_metadef_namespace_resource_types_table(meta)
121+
metadef_objects_table = get_metadef_objects_table(meta)
122+
metadef_properties_table = get_metadef_properties_table(meta)
123+
metadef_resource_types_table = get_metadef_resource_types_table(meta)
124+
125+
if not json_schema_files:
126+
LOG.error(_LE("Json schema files not found in %s. Aborting."),
127+
metadata_path)
128+
return
129+
130+
for namespace_id, json_schema_file in enumerate(json_schema_files,
131+
start=1):
132+
try:
133+
file = join(metadata_path, json_schema_file)
134+
json_metadata = open(file)
135+
metadata = json.load(json_metadata)
136+
json_metadata.close()
137+
except Exception as e:
138+
LOG.error(utils.exception_to_str(e))
139+
continue
140+
141+
values = {
142+
'id': namespace_id,
143+
'namespace': metadata.get('namespace', None),
144+
'display_name': metadata.get('display_name', None),
145+
'description': metadata.get('description', None),
146+
'visibility': metadata.get('visibility', None),
147+
'protected': metadata.get('protected', None),
148+
'owner': metadata.get('owner', 'admin'),
149+
'created_at': timeutils.utcnow()
150+
}
151+
_insert_data_to_db(metadef_namespaces_table, values)
152+
153+
for resource_type in metadata.get('resource_type_associations', []):
154+
try:
155+
resource_type_id = \
156+
_get_resource_type_id(meta, resource_type['name'])
157+
except AttributeError:
158+
values = {
159+
'name': resource_type['name'],
160+
'protected': True,
161+
'created_at': timeutils.utcnow()
162+
}
163+
_insert_data_to_db(metadef_resource_types_table,
164+
values)
165+
resource_type_id =\
166+
_get_resource_type_id(meta, resource_type['name'])
167+
168+
values = {
169+
'resource_type_id': resource_type_id,
170+
'namespace_id': namespace_id,
171+
'created_at': timeutils.utcnow(),
172+
'properties_target': resource_type.get('properties_target'),
173+
'prefix': resource_type.get('prefix', None)
174+
}
175+
_insert_data_to_db(metadef_namespace_resource_types_tables,
176+
values)
177+
178+
for property, schema in metadata.get('properties', {}).iteritems():
179+
values = {
180+
'name': property,
181+
'namespace_id': namespace_id,
182+
'schema': json.dumps(schema),
183+
'created_at': timeutils.utcnow()
184+
}
185+
_insert_data_to_db(metadef_properties_table, values)
186+
187+
for object in metadata.get('objects', []):
188+
values = {
189+
'name': object.get('name', None),
190+
'description': object.get('description', None),
191+
'namespace_id': namespace_id,
192+
'schema': json.dumps(object.get('properties', None)),
193+
'created_at': timeutils.utcnow()
194+
}
195+
_insert_data_to_db(metadef_objects_table, values)
196+
197+
LOG.info(_LI("File %s loaded to database."), file)
198+
199+
LOG.info(_LI("Metadata loading finished"))
200+
201+
202+
def _clear_metadata(meta):
203+
metadef_tables = [get_metadef_properties_table(meta),
204+
get_metadef_objects_table(meta),
205+
get_metadef_namespace_resource_types_table(meta),
206+
get_metadef_namespaces_table(meta)]
207+
208+
for table in metadef_tables:
209+
table.delete().execute()
210+
LOG.info(_LI("Table %s has been cleared"), table)
211+
212+
213+
def _insert_data_to_db(table, values, log_exception=True):
214+
try:
215+
table.insert(values=values).execute()
216+
except sqlalchemy.exc.IntegrityError:
217+
if log_exception:
218+
LOG.warning(_LW("Duplicate entry for values: %s"), values)
219+
220+
221+
def _export_data_to_file(meta, path):
222+
if not path:
223+
path = CONF.metadata_source_path
224+
225+
namespace_table = get_metadef_namespaces_table(meta)
226+
namespaces = namespace_table.select().execute().fetchall()
227+
228+
pattern = re.compile('[\W_]+', re.UNICODE)
229+
230+
for id, namespace in enumerate(namespaces, start=1):
231+
namespace_id = namespace['id']
232+
namespace_file_name = pattern.sub('', namespace['display_name'])
233+
234+
values = {
235+
'namespace': namespace['namespace'],
236+
'display_name': namespace['display_name'],
237+
'description': namespace['description'],
238+
'visibility': namespace['visibility'],
239+
'protected': namespace['protected'],
240+
'owner': namespace['owner'],
241+
'resource_type_associations': [],
242+
'properties': {},
243+
'objects': []
244+
}
245+
246+
namespace_resource_types = _get_namespace_resource_types(meta,
247+
namespace_id)
248+
db_objects = _get_objects(meta, namespace_id)
249+
db_properties = _get_properties(meta, namespace_id)
250+
251+
resource_types = []
252+
for namespace_resource_type in namespace_resource_types:
253+
resource_type =\
254+
_get_resource_type(meta,
255+
namespace_resource_type['resource_type_id'])
256+
resource_types.append({
257+
'name': resource_type['name'],
258+
'protected': resource_type['protected']
259+
})
260+
values.update({
261+
'resource_type_associations': resource_types
262+
})
263+
264+
objects = []
265+
for object in db_objects:
266+
objects.append({
267+
"name": object['name'],
268+
"description": object['description'],
269+
"properties": json.loads(object['schema'])
270+
})
271+
values.update({
272+
'objects': objects
273+
})
274+
275+
properties = {}
276+
for property in db_properties:
277+
properties.update({
278+
property['name']: json.loads(property['schema'])
279+
})
280+
values.update({
281+
'properties': properties
282+
})
283+
284+
try:
285+
file_name = ''.join([path, namespace_file_name, '.json'])
286+
json_file = open(file_name, 'w+')
287+
json_file.write(json.dumps(values))
288+
json_file.close()
289+
290+
except Exception as e:
291+
LOG.exception(utils.exception_to_str(e))
292+
LOG.info(_LI("Namespace %s saved in %s"),
293+
namespace_file_name, file_name)
294+
295+
296+
def db_load_metadefs(engine, metadata_path=None):
297+
meta = MetaData()
298+
meta.bind = engine
299+
300+
_populate_metadata(meta, metadata_path)
301+
302+
303+
def db_unload_metadefs(engine):
304+
meta = MetaData()
305+
meta.bind = engine
306+
307+
_clear_metadata(meta)
308+
309+
310+
def db_export_metadefs(engine, metadata_path=None):
311+
meta = MetaData()
312+
meta.bind = engine
313+
314+
_export_data_to_file(meta, metadata_path)

‎glance/tests/unit/test_manage.py

+73
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from glance.cmd import manage
2222
from glance.db import migration as db_migration
2323
from glance.db.sqlalchemy import api as db_api
24+
from glance.db.sqlalchemy import metadata as db_metadata
2425

2526

2627
class TestManageBase(testtools.TestCase):
@@ -127,6 +128,42 @@ def test_legacy_db_downgrade_version_without_sanity_check(self, db_sync):
127128
db_migration.MIGRATE_REPO_PATH, '20',
128129
sanity_check=False)
129130

131+
def test_db_metadefs_unload(self):
132+
db_metadata.db_unload_metadefs = mock.Mock()
133+
self._main_test_helper(['glance.cmd.manage', 'db_unload_metadefs'],
134+
db_metadata.db_unload_metadefs,
135+
db_api.get_engine())
136+
137+
def test_db_metadefs_load(self):
138+
db_metadata.db_load_metadefs = mock.Mock()
139+
self._main_test_helper(['glance.cmd.manage', 'db_load_metadefs'],
140+
db_metadata.db_load_metadefs,
141+
db_api.get_engine(),
142+
None)
143+
144+
def test_db_metadefs_load_with_specified_path(self):
145+
db_metadata.db_load_metadefs = mock.Mock()
146+
self._main_test_helper(['glance.cmd.manage', 'db_load_metadefs',
147+
'/mock/'],
148+
db_metadata.db_load_metadefs,
149+
db_api.get_engine(),
150+
'/mock/')
151+
152+
def test_db_metadefs_export(self):
153+
db_metadata.db_export_metadefs = mock.Mock()
154+
self._main_test_helper(['glance.cmd.manage', 'db_export_metadefs'],
155+
db_metadata.db_export_metadefs,
156+
db_api.get_engine(),
157+
None)
158+
159+
def test_db_metadefs_export_with_specified_path(self):
160+
db_metadata.db_export_metadefs = mock.Mock()
161+
self._main_test_helper(['glance.cmd.manage', 'db_export_metadefs',
162+
'/mock/'],
163+
db_metadata.db_export_metadefs,
164+
db_api.get_engine(),
165+
'/mock/')
166+
130167

131168
class TestManage(TestManageBase):
132169

@@ -210,3 +247,39 @@ def test_db_downgrade_version_without_sanity_check(self, db_sync):
210247
db_api.get_engine(),
211248
db_migration.MIGRATE_REPO_PATH, '20',
212249
sanity_check=False)
250+
251+
def test_db_metadefs_unload(self):
252+
db_metadata.db_unload_metadefs = mock.Mock()
253+
self._main_test_helper(['glance.cmd.manage', 'db', 'unload_metadefs'],
254+
db_metadata.db_unload_metadefs,
255+
db_api.get_engine())
256+
257+
def test_db_metadefs_load(self):
258+
db_metadata.db_load_metadefs = mock.Mock()
259+
self._main_test_helper(['glance.cmd.manage', 'db', 'load_metadefs'],
260+
db_metadata.db_load_metadefs,
261+
db_api.get_engine(),
262+
None)
263+
264+
def test_db_metadefs_load_with_specified_path(self):
265+
db_metadata.db_load_metadefs = mock.Mock()
266+
self._main_test_helper(['glance.cmd.manage', 'db', 'load_metadefs',
267+
'--path', '/mock/'],
268+
db_metadata.db_load_metadefs,
269+
db_api.get_engine(),
270+
'/mock/')
271+
272+
def test_db_metadefs_export(self):
273+
db_metadata.db_export_metadefs = mock.Mock()
274+
self._main_test_helper(['glance.cmd.manage', 'db', 'export_metadefs'],
275+
db_metadata.db_export_metadefs,
276+
db_api.get_engine(),
277+
None)
278+
279+
def test_db_metadefs_export_with_specified_path(self):
280+
db_metadata.db_export_metadefs = mock.Mock()
281+
self._main_test_helper(['glance.cmd.manage', 'db', 'export_metadefs',
282+
'--path', '/mock/'],
283+
db_metadata.db_export_metadefs,
284+
db_api.get_engine(),
285+
'/mock/')

0 commit comments

Comments
 (0)
Please sign in to comment.