Merge lp:~soren/nova/raw-disk-images into lp:~hudson-openstack/nova/trunk

Proposed by Soren Hansen
Status: Merged
Approved by: Soren Hansen
Approved revision: 439
Merged at revision: 471
Proposed branch: lp:~soren/nova/raw-disk-images
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 832 lines (+264/-268)
15 files modified
.mailmap (+1/-0)
MANIFEST.in (+1/-1)
nova/api/ec2/cloud.py (+1/-1)
nova/compute/api.py (+11/-6)
nova/compute/disk.py (+19/-3)
nova/flags.py (+3/-4)
nova/tests/virt_unittest.py (+108/-24)
nova/virt/libvirt.rescue.qemu.xml.template (+0/-37)
nova/virt/libvirt.rescue.uml.xml.template (+0/-26)
nova/virt/libvirt.rescue.xen.xml.template (+0/-34)
nova/virt/libvirt.uml.xml.template (+0/-26)
nova/virt/libvirt.xen.xml.template (+0/-30)
nova/virt/libvirt.xml.template (+61/-18)
nova/virt/libvirt_conn.py (+58/-58)
tools/pip-requires (+1/-0)
To merge this branch: bzr merge lp:~soren/nova/raw-disk-images
Reviewer Review Type Date Requested Status
Jay Pipes (community) Approve
Vish Ishaya (community) Approve
Salvatore Orlando (community) Needs Information
Rick Clark (community) Approve
Review via email: mp+43712@code.launchpad.net

Commit message

Add raw disk image support.

Description of the change

This branch adds raw disk image support. It was originally implemented by Justin Santa Barbara, then forward-ported by Rick Clark, and now I (believe I) finished it off.

To post a comment you must log in.
Revision history for this message
Rick Clark (dendrobates) wrote :

Like the new templates.

review: Approve
Revision history for this message
Soren Hansen (soren) wrote :

I need to make sure this works after the eventlet merge..

Revision history for this message
Soren Hansen (soren) wrote :

Should be fine now.

Revision history for this message
Salvatore Orlando (salvatore-orlando) wrote :

Hi Soren,

I checked out your code, and I am not able to launch raw images through the EC2 api.
When I use ' --kernel nokernel', I get the following error:
ERROR:root:NotFound: Image nokernel could not be found

I believe this happens because the image service still tries to retrieve the kernel image.
from compute/api.py lines 86-87:
            if kernel_id:
                self.image_service.show(context, kernel_id)

In the original branch from justinsb, kernel and ramdisk images were not retrieve when the null_kernel 'magic number' was used.

            if kernel_id == str(FLAGS.null_kernel):
                kernel_id = None
                ramdisk_id = None
                logging.debug("Creating a raw instance")
            # Make sure we have access to kernel and ramdisk (if not raw)
            if kernel_id:
                image_service.show(context, kernel_id)
            if ramdisk_id:
                image_service.show(context, ramdisk_id)

Is there something I'm not seeing at the moment, or do we need to fix compute/api.py?

review: Needs Information
Revision history for this message
Rick Clark (dendrobates) wrote :

Salvatore,
I have reproduced your problem. Soren is now out for Christmas holiday. I suggest that we merge this to clear up the queue and then fix the bug. It does not break existing behavior. Waiting for Soren to fix this branch will delay merging an the fix to api.py should be simple.

Revision history for this message
Thierry Carrez (ttx) wrote :

+1, just make sure we have a bug filed to remember to fix that one.

Revision history for this message
Salvatore Orlando (salvatore-orlando) wrote :

Hi Rick,

I fixed it in my brach which has been proposed for merge.

Revision history for this message
Vish Ishaya (vishvananda) wrote :

Lets get this in and aadd salvatore's fix.

review: Approve
Revision history for this message
Jay Pipes (jaypipes) wrote :

Looks great. One tiny pep8 fix to make, though:

jpipes@serialcoder:~/repos/nova/raw-disk-images$ pep8 bin/* nova
nova/compute/disk.py:108:1: E302 expected 2 blank lines, found 1

review: Needs Fixing
Revision history for this message
Soren Hansen (soren) wrote :

Fixed.

Revision history for this message
Jay Pipes (jaypipes) wrote :

w00t.

review: Approve
Revision history for this message
OpenStack Infra (hudson-openstack) wrote :

The attempt to merge lp:~soren/nova/raw-disk-images into lp:nova failed. Below is the output from the failed tests.

Traceback (most recent call last):
  File "run_tests.py", line 71, in <module>
    from nova.tests.virt_unittest import *
  File "/tmp/tmpgcaUjT/nova/tests/virt_unittest.py", line 27, in <module>
    from nova.virt import libvirt_conn
  File "/tmp/tmpgcaUjT/nova/virt/libvirt_conn.py", line 60, in <module>
    from Cheetah.Template import Template
ImportError: No module named Cheetah.Template

Revision history for this message
Soren Hansen (soren) wrote :

soren@hudson:~$ sudo apt-get install python-cheetah
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following NEW packages will be installed:
  python-cheetah
0 upgraded, 1 newly installed, 0 to remove and 28 not upgraded.
Need to get 0B/199kB of archives.
After this operation, 1073kB of additional disk space will be used.
Selecting previously deselected package python-cheetah.
(Reading database ... 41319 files and directories currently installed.)
Unpacking python-cheetah (from .../python-cheetah_2.0.1-2ubuntu7_amd64.deb) ...
Processing triggers for man-db ...
Setting up python-cheetah (2.0.1-2ubuntu7) ...
Processing triggers for python-support ...
soren@hudson:~$

Retrying.

Revision history for this message
OpenStack Infra (hudson-openstack) wrote :
Download full text (12.8 KiB)

The attempt to merge lp:~soren/nova/raw-disk-images into lp:nova failed. Below is the output from the failed tests.

nova.tests.access_unittest
  AccessTestCase
    test_001_allow_all ... [OK]
    test_002_allow_none ... [OK]
    test_003_allow_project_manager ... [OK]
    test_004_allow_sys_and_net ... [OK]
nova.tests.api_unittest
  ApiEc2TestCase
    test_authorize_revoke_security_group_cidr ... [OK]
    test_authorize_revoke_security_group_foreign_group ... [OK]
    test_create_delete_security_group ... [OK]
    test_describe_instances ... [OK]
    test_get_all_key_pairs ... [OK]
    test_get_all_security_groups ... [OK]
  XmlConversionTestCase
    test_number_conversion ... [OK]
nova.tests.auth_unittest
  AuthManagerDbTestCase
    test_004_signature_is_valid ... [OK]
    test_005_can_get_credentials ... [OK]
    test_add_user_role_doesnt_infect_project_roles ... [OK]
    test_adding_role_to_project_is_ignored_unless_added_to_user ... [OK]
    test_can_add_and_remove_user_role ... [OK]
    test_can_add_remove_user_with_role ... [OK]
    test_can_add_user_to_project ... [OK]
    test_can_create_and_get_project ... [OK]
    test_can_create_and_get_project_with_attributes ... [OK]
    test_can_create_project_with_manager ... [OK]
    test_can_delete_project ... [OK]
    test_can_delete_user ... [OK]
    test_can_generate_x509 ... [OK]
    test_can_list_project_roles ... [OK]
    test_can_list_projects ... [OK]
    test_can_list_user_roles ... [OK]
    test_can_list_users ... [OK]
    test_can_modify_project ... [OK]
    test_can_modify_users ... [OK]
    test_can_remove_project_role_but_keep_user_role ... [OK]
    test_can_remove_user_from_project ... [OK]
    test_can_remove_user_roles ... [OK]
    test_can_retrieve_project_by_user ... [OK]
    test_create_and_find_user ... [OK]
    test_create_and_find_with_properties ... [OK]
    test_create_project_assigns_manager_to_me...

lp:~soren/nova/raw-disk-images updated
439. By Soren Hansen

Add my @linux2go.dk address to .mailmap

Revision history for this message
Soren Hansen (soren) wrote :

I suck.

Note to self: Running tests before committing to make sure everything is fine isn't enough. *sigh*

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file '.mailmap'
--- .mailmap 2010-11-23 21:34:53 +0000
+++ .mailmap 2010-12-20 21:55:17 +0000
@@ -19,6 +19,7 @@
19<mordred@inaugust.com> <mordred@hudson>19<mordred@inaugust.com> <mordred@hudson>
20<paul@openstack.org> <pvoccio@castor.local>20<paul@openstack.org> <pvoccio@castor.local>
21<paul@openstack.org> <paul.voccio@rackspace.com>21<paul@openstack.org> <paul.voccio@rackspace.com>
22<soren.hansen@rackspace.com> <soren@linux2go.dk>
22<todd@ansolabs.com> <todd@lapex>23<todd@ansolabs.com> <todd@lapex>
23<todd@ansolabs.com> <todd@rubidine.com>24<todd@ansolabs.com> <todd@rubidine.com>
24<vishvananda@gmail.com> <vishvananda@yahoo.com>25<vishvananda@gmail.com> <vishvananda@yahoo.com>
2526
=== modified file 'MANIFEST.in'
--- MANIFEST.in 2010-11-20 21:26:15 +0000
+++ MANIFEST.in 2010-12-20 21:55:17 +0000
@@ -13,7 +13,7 @@
13include nova/compute/fakevirtinstance.xml13include nova/compute/fakevirtinstance.xml
14include nova/compute/interfaces.template14include nova/compute/interfaces.template
15include nova/virt/interfaces.template15include nova/virt/interfaces.template
16include nova/virt/libvirt.*.xml.template16include nova/virt/libvirt*.xml.template
17include nova/tests/CA/17include nova/tests/CA/
18include nova/tests/CA/cacert.pem18include nova/tests/CA/cacert.pem
19include nova/tests/CA/private/19include nova/tests/CA/private/
2020
=== modified file 'nova/api/ec2/cloud.py'
--- nova/api/ec2/cloud.py 2010-12-09 17:30:03 +0000
+++ nova/api/ec2/cloud.py 2010-12-20 21:55:17 +0000
@@ -751,7 +751,7 @@
751 kwargs['image_id'],751 kwargs['image_id'],
752 min_count=int(kwargs.get('min_count', max_count)),752 min_count=int(kwargs.get('min_count', max_count)),
753 max_count=max_count,753 max_count=max_count,
754 kernel_id=kwargs.get('kernel_id'),754 kernel_id=kwargs.get('kernel_id', None),
755 ramdisk_id=kwargs.get('ramdisk_id'),755 ramdisk_id=kwargs.get('ramdisk_id'),
756 display_name=kwargs.get('display_name'),756 display_name=kwargs.get('display_name'),
757 description=kwargs.get('display_description'),757 description=kwargs.get('display_description'),
758758
=== modified file 'nova/compute/api.py'
--- nova/compute/api.py 2010-12-14 21:56:42 +0000
+++ nova/compute/api.py 2010-12-20 21:55:17 +0000
@@ -73,14 +73,19 @@
73 is_vpn = image_id == FLAGS.vpn_image_id73 is_vpn = image_id == FLAGS.vpn_image_id
74 if not is_vpn:74 if not is_vpn:
75 image = self.image_service.show(context, image_id)75 image = self.image_service.show(context, image_id)
76
77 # If kernel_id/ramdisk_id isn't explicitly set in API call
78 # we take the defaults from the image's metadata
76 if kernel_id is None:79 if kernel_id is None:
77 kernel_id = image.get('kernelId', FLAGS.default_kernel)80 kernel_id = image.get('kernelId', None)
78 if ramdisk_id is None:81 if ramdisk_id is None:
79 ramdisk_id = image.get('ramdiskId', FLAGS.default_ramdisk)82 ramdisk_id = image.get('ramdiskId', None)
8083
81 # Make sure we have access to kernel and ramdisk84 # Make sure we have access to kernel and ramdisk
82 self.image_service.show(context, kernel_id)85 if kernel_id:
83 self.image_service.show(context, ramdisk_id)86 self.image_service.show(context, kernel_id)
87 if ramdisk_id:
88 self.image_service.show(context, ramdisk_id)
8489
85 if security_group is None:90 if security_group is None:
86 security_group = ['default']91 security_group = ['default']
@@ -103,8 +108,8 @@
103 base_options = {108 base_options = {
104 'reservation_id': utils.generate_uid('r'),109 'reservation_id': utils.generate_uid('r'),
105 'image_id': image_id,110 'image_id': image_id,
106 'kernel_id': kernel_id,111 'kernel_id': kernel_id or '',
107 'ramdisk_id': ramdisk_id,112 'ramdisk_id': ramdisk_id or '',
108 'state_description': 'scheduling',113 'state_description': 'scheduling',
109 'user_id': context.user_id,114 'user_id': context.user_id,
110 'project_id': context.project_id,115 'project_id': context.project_id,
111116
=== modified file 'nova/compute/disk.py'
--- nova/compute/disk.py 2010-12-08 20:59:22 +0000
+++ nova/compute/disk.py 2010-12-20 21:55:17 +0000
@@ -106,6 +106,13 @@
106 % (outfile, local_type, local_first, local_last))106 % (outfile, local_type, local_first, local_last))
107107
108108
109def extend(image, size, execute):
110 file_size = os.path.getsize(image)
111 if file_size >= size:
112 return
113 return execute('truncate -s size %s' % (image,))
114
115
109def inject_data(image, key=None, net=None, partition=None, execute=None):116def inject_data(image, key=None, net=None, partition=None, execute=None):
110 """Injects a ssh key and optionally net data into a disk image.117 """Injects a ssh key and optionally net data into a disk image.
111118
@@ -115,7 +122,7 @@
115 If partition is not specified it mounts the image as a single partition.122 If partition is not specified it mounts the image as a single partition.
116123
117 """124 """
118 out, err = execute('sudo losetup -f --show %s' % image)125 out, err = execute('sudo losetup --find --show %s' % image)
119 if err:126 if err:
120 raise exception.Error('Could not attach image to loopback: %s' % err)127 raise exception.Error('Could not attach image to loopback: %s' % err)
121 device = out.strip()128 device = out.strip()
@@ -129,6 +136,15 @@
129 partition)136 partition)
130 else:137 else:
131 mapped_device = device138 mapped_device = device
139
140 # We can only loopback mount raw images. If the device isn't there,
141 # it's normally because it's a .vmdk or a .vdi etc
142 if not os.path.exists(mapped_device):
143 raise exception.Error('Mapped device was not found (we can'
144 ' only inject raw disk images): %s' %
145 mapped_device)
146
147 # Configure ext2fs so that it doesn't auto-check every N boots
132 out, err = execute('sudo tune2fs -c 0 -i 0 %s' % mapped_device)148 out, err = execute('sudo tune2fs -c 0 -i 0 %s' % mapped_device)
133149
134 tmpdir = tempfile.mkdtemp()150 tmpdir = tempfile.mkdtemp()
@@ -156,7 +172,7 @@
156 execute('sudo kpartx -d %s' % device)172 execute('sudo kpartx -d %s' % device)
157 finally:173 finally:
158 # remove loopback174 # remove loopback
159 execute('sudo losetup -d %s' % device)175 execute('sudo losetup --detach %s' % device)
160176
161177
162def _inject_key_into_fs(key, fs, execute=None):178def _inject_key_into_fs(key, fs, execute=None):
@@ -165,7 +181,7 @@
165 key is an ssh key string.181 key is an ssh key string.
166 fs is the path to the base of the filesystem into which to inject the key.182 fs is the path to the base of the filesystem into which to inject the key.
167 """183 """
168 sshdir = os.path.join(os.path.join(fs, 'root'), '.ssh')184 sshdir = os.path.join(fs, 'root', '.ssh')
169 execute('sudo mkdir -p %s' % sshdir) # existing dir doesn't matter185 execute('sudo mkdir -p %s' % sshdir) # existing dir doesn't matter
170 execute('sudo chown root %s' % sshdir)186 execute('sudo chown root %s' % sshdir)
171 execute('sudo chmod 700 %s' % sshdir)187 execute('sudo chmod 700 %s' % sshdir)
172188
=== modified file 'nova/flags.py'
--- nova/flags.py 2010-12-10 00:05:13 +0000
+++ nova/flags.py 2010-12-20 21:55:17 +0000
@@ -235,12 +235,11 @@
235235
236DEFINE_string('default_image', 'ami-11111',236DEFINE_string('default_image', 'ami-11111',
237 'default image to use, testing only')237 'default image to use, testing only')
238DEFINE_string('default_kernel', 'aki-11111',
239 'default kernel to use, testing only')
240DEFINE_string('default_ramdisk', 'ari-11111',
241 'default ramdisk to use, testing only')
242DEFINE_string('default_instance_type', 'm1.small',238DEFINE_string('default_instance_type', 'm1.small',
243 'default instance type to use, testing only')239 'default instance type to use, testing only')
240DEFINE_string('null_kernel', 'nokernel',
241 'kernel image that indicates not to use a kernel,'
242 ' but to use a raw disk image instead')
244243
245DEFINE_string('vpn_image_id', 'ami-CLOUDPIPE', 'AMI for cloudpipe vpn server')244DEFINE_string('vpn_image_id', 'ami-CLOUDPIPE', 'AMI for cloudpipe vpn server')
246DEFINE_string('vpn_key_suffix',245DEFINE_string('vpn_key_suffix',
247246
=== modified file 'nova/tests/virt_unittest.py'
--- nova/tests/virt_unittest.py 2010-12-10 00:05:13 +0000
+++ nova/tests/virt_unittest.py 2010-12-20 21:55:17 +0000
@@ -40,19 +40,51 @@
40 self.network = utils.import_object(FLAGS.network_manager)40 self.network = utils.import_object(FLAGS.network_manager)
41 FLAGS.instances_path = ''41 FLAGS.instances_path = ''
4242
43 def test_get_uri_and_template(self):43 test_ip = '10.11.12.13'
44 ip = '10.11.12.13'44 test_instance = {'memory_kb': '1024000',
4545 'basepath': '/some/path',
46 instance = {'internal_id': 1,46 'bridge_name': 'br100',
47 'memory_kb': '1024000',47 'mac_address': '02:12:34:46:56:67',
48 'basepath': '/some/path',48 'vcpus': 2,
49 'bridge_name': 'br100',49 'project_id': 'fake',
50 'mac_address': '02:12:34:46:56:67',50 'bridge': 'br101',
51 'vcpus': 2,51 'instance_type': 'm1.small'}
52 'project_id': 'fake',52
53 'bridge': 'br101',53 def test_xml_and_uri_no_ramdisk_no_kernel(self):
54 'instance_type': 'm1.small'}54 instance_data = dict(self.test_instance)
5555 self.do_test_xml_and_uri(instance_data,
56 expect_kernel=False, expect_ramdisk=False)
57
58 def test_xml_and_uri_no_ramdisk(self):
59 instance_data = dict(self.test_instance)
60 instance_data['kernel_id'] = 'aki-deadbeef'
61 self.do_test_xml_and_uri(instance_data,
62 expect_kernel=True, expect_ramdisk=False)
63
64 def test_xml_and_uri_no_kernel(self):
65 instance_data = dict(self.test_instance)
66 instance_data['ramdisk_id'] = 'ari-deadbeef'
67 self.do_test_xml_and_uri(instance_data,
68 expect_kernel=False, expect_ramdisk=False)
69
70 def test_xml_and_uri(self):
71 instance_data = dict(self.test_instance)
72 instance_data['ramdisk_id'] = 'ari-deadbeef'
73 instance_data['kernel_id'] = 'aki-deadbeef'
74 self.do_test_xml_and_uri(instance_data,
75 expect_kernel=True, expect_ramdisk=True)
76
77 def test_xml_and_uri_rescue(self):
78 instance_data = dict(self.test_instance)
79 instance_data['ramdisk_id'] = 'ari-deadbeef'
80 instance_data['kernel_id'] = 'aki-deadbeef'
81 self.do_test_xml_and_uri(instance_data,
82 expect_kernel=True, expect_ramdisk=True,
83 rescue=True)
84
85 def do_test_xml_and_uri(self, instance,
86 expect_ramdisk, expect_kernel,
87 rescue=False):
56 user_context = context.RequestContext(project=self.project,88 user_context = context.RequestContext(project=self.project,
57 user=self.user)89 user=self.user)
58 instance_ref = db.instance_create(user_context, instance)90 instance_ref = db.instance_create(user_context, instance)
@@ -60,13 +92,14 @@
60 self.network.set_network_host(context.get_admin_context(),92 self.network.set_network_host(context.get_admin_context(),
61 network_ref['id'])93 network_ref['id'])
6294
63 fixed_ip = {'address': ip,95 fixed_ip = {'address': self.test_ip,
64 'network_id': network_ref['id']}96 'network_id': network_ref['id']}
6597
66 ctxt = context.get_admin_context()98 ctxt = context.get_admin_context()
67 fixed_ip_ref = db.fixed_ip_create(ctxt, fixed_ip)99 fixed_ip_ref = db.fixed_ip_create(ctxt, fixed_ip)
68 db.fixed_ip_update(ctxt, ip, {'allocated': True,100 db.fixed_ip_update(ctxt, self.test_ip,
69 'instance_id': instance_ref['id']})101 {'allocated': True,
102 'instance_id': instance_ref['id']})
70103
71 type_uri_map = {'qemu': ('qemu:///system',104 type_uri_map = {'qemu': ('qemu:///system',
72 [(lambda t: t.find('.').get('type'), 'qemu'),105 [(lambda t: t.find('.').get('type'), 'qemu'),
@@ -78,23 +111,71 @@
78 (lambda t: t.find('./devices/emulator'), None)]),111 (lambda t: t.find('./devices/emulator'), None)]),
79 'uml': ('uml:///system',112 'uml': ('uml:///system',
80 [(lambda t: t.find('.').get('type'), 'uml'),113 [(lambda t: t.find('.').get('type'), 'uml'),
81 (lambda t: t.find('./os/type').text, 'uml')])}114 (lambda t: t.find('./os/type').text, 'uml')]),
115 'xen': ('xen:///',
116 [(lambda t: t.find('.').get('type'), 'xen'),
117 (lambda t: t.find('./os/type').text, 'linux')]),
118 }
119
120 for hypervisor_type in ['qemu', 'kvm', 'xen']:
121 check_list = type_uri_map[hypervisor_type][1]
122
123 if rescue:
124 check = (lambda t: t.find('./os/kernel').text.split('/')[1],
125 'rescue-kernel')
126 check_list.append(check)
127 check = (lambda t: t.find('./os/initrd').text.split('/')[1],
128 'rescue-ramdisk')
129 check_list.append(check)
130 else:
131 if expect_kernel:
132 check = (lambda t: t.find('./os/kernel').text.split('/'
133 )[1], 'kernel')
134 else:
135 check = (lambda t: t.find('./os/kernel'), None)
136 check_list.append(check)
137
138 if expect_ramdisk:
139 check = (lambda t: t.find('./os/initrd').text.split('/'
140 )[1], 'ramdisk')
141 else:
142 check = (lambda t: t.find('./os/initrd'), None)
143 check_list.append(check)
82144
83 common_checks = [145 common_checks = [
84 (lambda t: t.find('.').tag, 'domain'),146 (lambda t: t.find('.').tag, 'domain'),
85 (lambda t: t.find('./devices/interface/filterref/parameter').\147 (lambda t: t.find('./devices/interface/filterref/parameter'
86 get('name'), 'IP'),148 ).get('name'), 'IP'),
87 (lambda t: t.find('./devices/interface/filterref/parameter').\149 (lambda t: t.find('./devices/interface/filterref/parameter'
88 get('value'), '10.11.12.13')]150 ).get('value'), '10.11.12.13'),
151 (lambda t: t.findall('./devices/interface/filterref/parameter'
152 )[1].get('name'), 'DHCPSERVER'),
153 (lambda t: t.findall('./devices/interface/filterref/parameter'
154 )[1].get('value'), '10.0.0.1'),
155 (lambda t: t.find('./devices/serial/source').get('path'
156 ).split('/')[1], 'console.log'),
157 (lambda t: t.find('./memory').text, '2097152')]
158
159 if rescue:
160 common_checks += [(lambda t: t.findall('./devices/disk/source'
161 )[0].get('file').split('/')[1],
162 'rescue-disk'),
163 (lambda t: t.findall('./devices/disk/source'
164 )[1].get('file').split('/')[1],
165 'disk')]
166 else:
167 common_checks += [(lambda t: t.findall('./devices/disk/source'
168 )[0].get('file').split('/')[1],
169 'disk')]
89170
90 for (libvirt_type, (expected_uri, checks)) in type_uri_map.iteritems():171 for (libvirt_type, (expected_uri, checks)) in type_uri_map.iteritems():
91 FLAGS.libvirt_type = libvirt_type172 FLAGS.libvirt_type = libvirt_type
92 conn = libvirt_conn.LibvirtConnection(True)173 conn = libvirt_conn.LibvirtConnection(True)
93174
94 uri, _template, _rescue = conn.get_uri_and_templates()175 uri = conn.get_uri()
95 self.assertEquals(uri, expected_uri)176 self.assertEquals(uri, expected_uri)
96177
97 xml = conn.to_xml(instance_ref)178 xml = conn.to_xml(instance_ref, rescue)
98 tree = xml_to_tree(xml)179 tree = xml_to_tree(xml)
99 for i, (check, expected_result) in enumerate(checks):180 for i, (check, expected_result) in enumerate(checks):
100 self.assertEqual(check(tree),181 self.assertEqual(check(tree),
@@ -106,6 +187,9 @@
106 expected_result,187 expected_result,
107 '%s failed common check %d' % (xml, i))188 '%s failed common check %d' % (xml, i))
108189
190 # This test is supposed to make sure we don't override a specifically
191 # set uri
192 #
109 # Deliberately not just assigning this string to FLAGS.libvirt_uri and193 # Deliberately not just assigning this string to FLAGS.libvirt_uri and
110 # checking against that later on. This way we make sure the194 # checking against that later on. This way we make sure the
111 # implementation doesn't fiddle around with the FLAGS.195 # implementation doesn't fiddle around with the FLAGS.
@@ -114,7 +198,7 @@
114 for (libvirt_type, (expected_uri, checks)) in type_uri_map.iteritems():198 for (libvirt_type, (expected_uri, checks)) in type_uri_map.iteritems():
115 FLAGS.libvirt_type = libvirt_type199 FLAGS.libvirt_type = libvirt_type
116 conn = libvirt_conn.LibvirtConnection(True)200 conn = libvirt_conn.LibvirtConnection(True)
117 uri, _template, _rescue = conn.get_uri_and_templates()201 uri = conn.get_uri()
118 self.assertEquals(uri, testuri)202 self.assertEquals(uri, testuri)
119203
120 def tearDown(self):204 def tearDown(self):
121205
=== removed file 'nova/virt/libvirt.rescue.qemu.xml.template'
--- nova/virt/libvirt.rescue.qemu.xml.template 2010-10-24 21:57:37 +0000
+++ nova/virt/libvirt.rescue.qemu.xml.template 1970-01-01 00:00:00 +0000
@@ -1,37 +0,0 @@
1<domain type='%(type)s'>
2 <name>%(name)s</name>
3 <os>
4 <type>hvm</type>
5 <kernel>%(basepath)s/rescue-kernel</kernel>
6 <initrd>%(basepath)s/rescue-ramdisk</initrd>
7 <cmdline>root=/dev/vda1 console=ttyS0</cmdline>
8 </os>
9 <features>
10 <acpi/>
11 </features>
12 <memory>%(memory_kb)s</memory>
13 <vcpu>%(vcpus)s</vcpu>
14 <devices>
15 <disk type='file'>
16 <source file='%(basepath)s/rescue-disk'/>
17 <target dev='vda' bus='virtio'/>
18 </disk>
19 <disk type='file'>
20 <source file='%(basepath)s/disk'/>
21 <target dev='vdb' bus='virtio'/>
22 </disk>
23 <interface type='bridge'>
24 <source bridge='%(bridge_name)s'/>
25 <mac address='%(mac_address)s'/>
26 <!-- <model type='virtio'/> CANT RUN virtio network right now -->
27 <filterref filter="nova-instance-%(name)s">
28 <parameter name="IP" value="%(ip_address)s" />
29 <parameter name="DHCPSERVER" value="%(dhcp_server)s" />
30 </filterref>
31 </interface>
32 <serial type="file">
33 <source path='%(basepath)s/console.log'/>
34 <target port='1'/>
35 </serial>
36 </devices>
37</domain>
380
=== removed file 'nova/virt/libvirt.rescue.uml.xml.template'
--- nova/virt/libvirt.rescue.uml.xml.template 2010-09-20 08:50:08 +0000
+++ nova/virt/libvirt.rescue.uml.xml.template 1970-01-01 00:00:00 +0000
@@ -1,26 +0,0 @@
1<domain type='%(type)s'>
2 <name>%(name)s</name>
3 <memory>%(memory_kb)s</memory>
4 <os>
5 <type>%(type)s</type>
6 <kernel>/usr/bin/linux</kernel>
7 <root>/dev/ubda1</root>
8 </os>
9 <devices>
10 <disk type='file'>
11 <source file='%(basepath)s/rescue-disk'/>
12 <target dev='ubd0' bus='uml'/>
13 </disk>
14 <disk type='file'>
15 <source file='%(basepath)s/disk'/>
16 <target dev='ubd1' bus='uml'/>
17 </disk>
18 <interface type='bridge'>
19 <source bridge='%(bridge_name)s'/>
20 <mac address='%(mac_address)s'/>
21 </interface>
22 <console type="file">
23 <source path='%(basepath)s/console.log'/>
24 </console>
25 </devices>
26</domain>
270
=== removed file 'nova/virt/libvirt.rescue.xen.xml.template'
--- nova/virt/libvirt.rescue.xen.xml.template 2010-10-25 00:18:24 +0000
+++ nova/virt/libvirt.rescue.xen.xml.template 1970-01-01 00:00:00 +0000
@@ -1,34 +0,0 @@
1<domain type='%(type)s'>
2 <name>%(name)s</name>
3 <os>
4 <type>linux</type>
5 <kernel>%(basepath)s/kernel</kernel>
6 <initrd>%(basepath)s/ramdisk</initrd>
7 <root>/dev/xvda1</root>
8 <cmdline>ro</cmdline>
9 </os>
10 <features>
11 <acpi/>
12 </features>
13 <memory>%(memory_kb)s</memory>
14 <vcpu>%(vcpus)s</vcpu>
15 <devices>
16 <disk type='file'>
17 <source file='%(basepath)s/rescue-disk'/>
18 <target dev='sda' />
19 </disk>
20 <disk type='file'>
21 <source file='%(basepath)s/disk'/>
22 <target dev='sdb' />
23 </disk>
24 <interface type='bridge'>
25 <source bridge='%(bridge_name)s'/>
26 <mac address='%(mac_address)s'/>
27 </interface>
28 <console type="file">
29 <source path='%(basepath)s/console.log'/>
30 <target port='1'/>
31 </console>
32 </devices>
33</domain>
34
350
=== removed file 'nova/virt/libvirt.uml.xml.template'
--- nova/virt/libvirt.uml.xml.template 2010-09-27 11:13:29 +0000
+++ nova/virt/libvirt.uml.xml.template 1970-01-01 00:00:00 +0000
@@ -1,26 +0,0 @@
1<domain type='%(type)s'>
2 <name>%(name)s</name>
3 <memory>%(memory_kb)s</memory>
4 <os>
5 <type>%(type)s</type>
6 <kernel>/usr/bin/linux</kernel>
7 <root>/dev/ubda1</root>
8 </os>
9 <devices>
10 <disk type='file'>
11 <source file='%(basepath)s/disk'/>
12 <target dev='ubd0' bus='uml'/>
13 </disk>
14 <interface type='bridge'>
15 <source bridge='%(bridge_name)s'/>
16 <mac address='%(mac_address)s'/>
17 <filterref filter="nova-instance-%(name)s">
18 <parameter name="IP" value="%(ip_address)s" />
19 <parameter name="DHCPSERVER" value="%(dhcp_server)s" />
20 </filterref>
21 </interface>
22 <console type="file">
23 <source path='%(basepath)s/console.log'/>
24 </console>
25 </devices>
26</domain>
270
=== removed file 'nova/virt/libvirt.xen.xml.template'
--- nova/virt/libvirt.xen.xml.template 2010-10-25 00:18:24 +0000
+++ nova/virt/libvirt.xen.xml.template 1970-01-01 00:00:00 +0000
@@ -1,30 +0,0 @@
1<domain type='%(type)s'>
2 <name>%(name)s</name>
3 <os>
4 <type>linux</type>
5 <kernel>%(basepath)s/kernel</kernel>
6 <initrd>%(basepath)s/ramdisk</initrd>
7 <root>/dev/xvda1</root>
8 <cmdline>ro</cmdline>
9 </os>
10 <features>
11 <acpi/>
12 </features>
13 <memory>%(memory_kb)s</memory>
14 <vcpu>%(vcpus)s</vcpu>
15 <devices>
16 <disk type='file'>
17 <source file='%(basepath)s/disk'/>
18 <target dev='sda' />
19 </disk>
20 <interface type='bridge'>
21 <source bridge='%(bridge_name)s'/>
22 <mac address='%(mac_address)s'/>
23 </interface>
24 <console type="file">
25 <source path='%(basepath)s/console.log'/>
26 <target port='1'/>
27 </console>
28 </devices>
29</domain>
30
310
=== renamed file 'nova/virt/libvirt.qemu.xml.template' => 'nova/virt/libvirt.xml.template'
--- nova/virt/libvirt.qemu.xml.template 2010-09-27 11:13:29 +0000
+++ nova/virt/libvirt.xml.template 2010-12-20 21:55:17 +0000
@@ -1,32 +1,75 @@
1<domain type='%(type)s'>1<domain type='${type}'>
2 <name>%(name)s</name>2 <name>${name}</name>
3 <memory>${memory_kb}</memory>
3 <os>4 <os>
4 <type>hvm</type>5#if $type == 'uml'
5 <kernel>%(basepath)s/kernel</kernel>6 #set $disk_prefix = 'ubd'
6 <initrd>%(basepath)s/ramdisk</initrd>7 #set $disk_bus = 'uml'
7 <cmdline>root=/dev/vda1 console=ttyS0</cmdline>8 <type>uml</type>
9 <kernel>/usr/bin/linux</kernel>
10 <root>/dev/ubda1</root>
11#else
12 #if $type == 'xen'
13 #set $disk_prefix = 'sd'
14 #set $disk_bus = 'scsi'
15 <type>linux</type>
16 <root>/dev/xvda1</root>
17 #else
18 #set $disk_prefix = 'vd'
19 #set $disk_bus = 'virtio'
20 <type>hvm</type>
21 #end if
22 #if $getVar('rescue', False)
23 <kernel>${basepath}/rescue-kernel</kernel>
24 <initrd>${basepath}/rescue-ramdisk</initrd>
25 #else
26 #if $getVar('kernel', None)
27 <kernel>${kernel}</kernel>
28 #if $type == 'xen'
29 <cmdline>ro</cmdline>
30 #else
31 <cmdline>root=/dev/vda1 console=ttyS0</cmdline>
32 #end if
33 #if $getVar('ramdisk', None)
34 <initrd>${ramdisk}</initrd>
35 #end if
36 #else
37 <boot dev="hd" />
38 #end if
39 #end if
40#end if
8 </os>41 </os>
9 <features>42 <features>
10 <acpi/>43 <acpi/>
11 </features>44 </features>
12 <memory>%(memory_kb)s</memory>45 <vcpu>${vcpus}</vcpu>
13 <vcpu>%(vcpus)s</vcpu>
14 <devices>46 <devices>
15 <disk type='file'>47#if $getVar('rescue', False)
16 <source file='%(basepath)s/disk'/>48 <disk type='file'>
17 <target dev='vda' bus='virtio'/>49 <source file='${basepath}/rescue-disk'/>
18 </disk>50 <target dev='${disk_prefix}a' bus='${disk_bus}'/>
51 </disk>
52 <disk type='file'>
53 <source file='${basepath}/disk'/>
54 <target dev='${disk_prefix}b' bus='${disk_bus}'/>
55 </disk>
56#else
57 <disk type='file'>
58 <source file='${basepath}/disk'/>
59 <target dev='${disk_prefix}a' bus='${disk_bus}'/>
60 </disk>
61#end if
19 <interface type='bridge'>62 <interface type='bridge'>
20 <source bridge='%(bridge_name)s'/>63 <source bridge='${bridge_name}'/>
21 <mac address='%(mac_address)s'/>64 <mac address='${mac_address}'/>
22 <!-- <model type='virtio'/> CANT RUN virtio network right now -->65 <!-- <model type='virtio'/> CANT RUN virtio network right now -->
23 <filterref filter="nova-instance-%(name)s">66 <filterref filter="nova-instance-${name}">
24 <parameter name="IP" value="%(ip_address)s" />67 <parameter name="IP" value="${ip_address}" />
25 <parameter name="DHCPSERVER" value="%(dhcp_server)s" />68 <parameter name="DHCPSERVER" value="${dhcp_server}" />
26 </filterref>69 </filterref>
27 </interface>70 </interface>
28 <serial type="file">71 <serial type="file">
29 <source path='%(basepath)s/console.log'/>72 <source path='${basepath}/console.log'/>
30 <target port='1'/>73 <target port='1'/>
31 </serial>74 </serial>
32 </devices>75 </devices>
3376
=== modified file 'nova/virt/libvirt_conn.py'
--- nova/virt/libvirt_conn.py 2010-12-16 00:31:32 +0000
+++ nova/virt/libvirt_conn.py 2010-12-20 21:55:17 +0000
@@ -27,12 +27,7 @@
27:libvirt_type: Libvirt domain type. Can be kvm, qemu, uml, xen27:libvirt_type: Libvirt domain type. Can be kvm, qemu, uml, xen
28 (default: kvm).28 (default: kvm).
29:libvirt_uri: Override for the default libvirt URI (depends on libvirt_type).29:libvirt_uri: Override for the default libvirt URI (depends on libvirt_type).
30:libvirt_xml_template: Libvirt XML Template (QEmu/KVM).30:libvirt_xml_template: Libvirt XML Template.
31:libvirt_xen_xml_template: Libvirt XML Template (Xen).
32:libvirt_uml_xml_template: Libvirt XML Template (User Mode Linux).
33:libvirt_rescue_xml_template: XML template for rescue mode (KVM & QEMU).
34:libvirt_rescue_xen_xml_template: XML templage for rescue mode (XEN).
35:libvirt_rescue_uml_xml_template: XML template for rescue mode (UML).
36:rescue_image_id: Rescue ami image (default: ami-rescue).31:rescue_image_id: Rescue ami image (default: ami-rescue).
37:rescue_kernel_id: Rescue aki image (default: aki-rescue).32:rescue_kernel_id: Rescue aki image (default: aki-rescue).
38:rescue_ramdisk_id: Rescue ari image (default: ari-rescue).33:rescue_ramdisk_id: Rescue ari image (default: ari-rescue).
@@ -62,36 +57,20 @@
62from nova.compute import power_state57from nova.compute import power_state
63from nova.virt import images58from nova.virt import images
6459
60from Cheetah.Template import Template
61
65libvirt = None62libvirt = None
66libxml2 = None63libxml2 = None
6764
6865
69FLAGS = flags.FLAGS66FLAGS = flags.FLAGS
70flags.DEFINE_string('libvirt_rescue_xml_template',
71 utils.abspath('virt/libvirt.rescue.qemu.xml.template'),
72 'Libvirt RESCUE XML Template for QEmu/KVM')
73flags.DEFINE_string('libvirt_rescue_xen_xml_template',
74 utils.abspath('virt/libvirt.rescue.xen.xml.template'),
75 'Libvirt RESCUE XML Template for xen')
76flags.DEFINE_string('libvirt_rescue_uml_xml_template',
77 utils.abspath('virt/libvirt.rescue.uml.xml.template'),
78 'Libvirt RESCUE XML Template for user-mode-linux')
79# TODO(vish): These flags should probably go into a shared location67# TODO(vish): These flags should probably go into a shared location
80flags.DEFINE_string('rescue_image_id', 'ami-rescue', 'Rescue ami image')68flags.DEFINE_string('rescue_image_id', 'ami-rescue', 'Rescue ami image')
81flags.DEFINE_string('rescue_kernel_id', 'aki-rescue', 'Rescue aki image')69flags.DEFINE_string('rescue_kernel_id', 'aki-rescue', 'Rescue aki image')
82flags.DEFINE_string('rescue_ramdisk_id', 'ari-rescue', 'Rescue ari image')70flags.DEFINE_string('rescue_ramdisk_id', 'ari-rescue', 'Rescue ari image')
83flags.DEFINE_string('libvirt_xml_template',71flags.DEFINE_string('libvirt_xml_template',
84 utils.abspath('virt/libvirt.qemu.xml.template'),72 utils.abspath('virt/libvirt.xml.template'),
85 'Libvirt XML Template for QEmu/KVM')73 'Libvirt XML Template')
86flags.DEFINE_string('libvirt_xen_xml_template',
87 utils.abspath('virt/libvirt.xen.xml.template'),
88 'Libvirt XML Template for Xen')
89flags.DEFINE_string('libvirt_uml_xml_template',
90 utils.abspath('virt/libvirt.uml.xml.template'),
91 'Libvirt XML Template for user-mode-linux')
92flags.DEFINE_string('injected_network_template',
93 utils.abspath('virt/interfaces.template'),
94 'Template file for injected network')
95flags.DEFINE_string('libvirt_type',74flags.DEFINE_string('libvirt_type',
96 'kvm',75 'kvm',
97 'Libvirt domain type (valid options are: '76 'Libvirt domain type (valid options are: '
@@ -120,12 +99,9 @@
120class LibvirtConnection(object):99class LibvirtConnection(object):
121100
122 def __init__(self, read_only):101 def __init__(self, read_only):
123 (self.libvirt_uri,102 self.libvirt_uri = self.get_uri()
124 template_file,
125 rescue_file) = self.get_uri_and_templates()
126103
127 self.libvirt_xml = open(template_file).read()104 self.libvirt_xml = open(FLAGS.libvirt_xml_template).read()
128 self.rescue_xml = open(rescue_file).read()
129 self._wrapped_conn = None105 self._wrapped_conn = None
130 self.read_only = read_only106 self.read_only = read_only
131107
@@ -148,20 +124,14 @@
148 return False124 return False
149 raise125 raise
150126
151 def get_uri_and_templates(self):127 def get_uri(self):
152 if FLAGS.libvirt_type == 'uml':128 if FLAGS.libvirt_type == 'uml':
153 uri = FLAGS.libvirt_uri or 'uml:///system'129 uri = FLAGS.libvirt_uri or 'uml:///system'
154 template_file = FLAGS.libvirt_uml_xml_template
155 rescue_file = FLAGS.libvirt_rescue_uml_xml_template
156 elif FLAGS.libvirt_type == 'xen':130 elif FLAGS.libvirt_type == 'xen':
157 uri = FLAGS.libvirt_uri or 'xen:///'131 uri = FLAGS.libvirt_uri or 'xen:///'
158 template_file = FLAGS.libvirt_xen_xml_template
159 rescue_file = FLAGS.libvirt_rescue_xen_xml_template
160 else:132 else:
161 uri = FLAGS.libvirt_uri or 'qemu:///system'133 uri = FLAGS.libvirt_uri or 'qemu:///system'
162 template_file = FLAGS.libvirt_xml_template134 return uri
163 rescue_file = FLAGS.libvirt_rescue_xml_template
164 return uri, template_file, rescue_file
165135
166 def _connect(self, uri, read_only):136 def _connect(self, uri, read_only):
167 auth = [[libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_NOECHOPROMPT],137 auth = [[libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_NOECHOPROMPT],
@@ -442,18 +412,28 @@
442 if not os.path.exists(basepath('disk')):412 if not os.path.exists(basepath('disk')):
443 images.fetch(inst.image_id, basepath('disk-raw'), user,413 images.fetch(inst.image_id, basepath('disk-raw'), user,
444 project)414 project)
445 if not os.path.exists(basepath('kernel')):415
446 images.fetch(inst.kernel_id, basepath('kernel'), user,416 if inst['kernel_id']:
447 project)417 if not os.path.exists(basepath('kernel')):
448 if not os.path.exists(basepath('ramdisk')):418 images.fetch(inst['kernel_id'], basepath('kernel'),
449 images.fetch(inst.ramdisk_id, basepath('ramdisk'), user,419 user, project)
450 project)420 if inst['ramdisk_id']:
421 if not os.path.exists(basepath('ramdisk')):
422 images.fetch(inst['ramdisk_id'], basepath('ramdisk'),
423 user, project)
451424
452 def execute(cmd, process_input=None, check_exit_code=True):425 def execute(cmd, process_input=None, check_exit_code=True):
453 return utils.execute(cmd=cmd,426 return utils.execute(cmd=cmd,
454 process_input=process_input,427 process_input=process_input,
455 check_exit_code=check_exit_code)428 check_exit_code=check_exit_code)
456429
430 # For now, we assume that if we're not using a kernel, we're using a
431 # partitioned disk image where the target partition is the first
432 # partition
433 target_partition = None
434 if not inst['kernel_id']:
435 target_partition = "1"
436
457 key = str(inst['key_data'])437 key = str(inst['key_data'])
458 net = None438 net = None
459 network_ref = db.network_get_by_instance(context.get_admin_context(),439 network_ref = db.network_get_by_instance(context.get_admin_context(),
@@ -473,12 +453,20 @@
473 inst['name'], inst.image_id)453 inst['name'], inst.image_id)
474 if net:454 if net:
475 logging.info('instance %s: injecting net into image %s',455 logging.info('instance %s: injecting net into image %s',
476 inst['name'], inst.image_id)456 inst['name'], inst.image_id)
477 disk.inject_data(basepath('disk-raw'), key, net,457 try:
478 execute=execute)458 disk.inject_data(basepath('disk-raw'), key, net,
459 partition=target_partition,
460 execute=execute)
461 except Exception as e:
462 # This could be a windows image, or a vmdk format disk
463 logging.warn('instance %s: ignoring error injecting data'
464 ' into image %s (%s)',
465 inst['name'], inst.image_id, e)
479466
480 if os.path.exists(basepath('disk')):467 if inst['kernel_id']:
481 utils.execute('rm -f %s' % basepath('disk'))468 if os.path.exists(basepath('disk')):
469 utils.execute('rm -f %s' % basepath('disk'))
482470
483 local_bytes = (instance_types.INSTANCE_TYPES[inst.instance_type]471 local_bytes = (instance_types.INSTANCE_TYPES[inst.instance_type]
484 ['local_gb']472 ['local_gb']
@@ -487,8 +475,13 @@
487 resize = True475 resize = True
488 if inst['instance_type'] == 'm1.tiny' or prefix == 'rescue-':476 if inst['instance_type'] == 'm1.tiny' or prefix == 'rescue-':
489 resize = False477 resize = False
490 disk.partition(basepath('disk-raw'), basepath('disk'),478
491 local_bytes, resize, execute=execute)479 if inst['kernel_id']:
480 disk.partition(basepath('disk-raw'), basepath('disk'),
481 local_bytes, resize, execute=execute)
482 else:
483 os.rename(basepath('disk-raw'), basepath('disk'))
484 disk.extend(basepath('disk'), local_bytes, execute=execute)
492485
493 if FLAGS.libvirt_type == 'uml':486 if FLAGS.libvirt_type == 'uml':
494 utils.execute('sudo chown root %s' % basepath('disk'))487 utils.execute('sudo chown root %s' % basepath('disk'))
@@ -514,14 +507,21 @@
514 'bridge_name': network['bridge'],507 'bridge_name': network['bridge'],
515 'mac_address': instance['mac_address'],508 'mac_address': instance['mac_address'],
516 'ip_address': ip_address,509 'ip_address': ip_address,
517 'dhcp_server': dhcp_server}510 'dhcp_server': dhcp_server,
518 if rescue:511 'rescue': rescue}
519 libvirt_xml = self.rescue_xml % xml_info512 if not rescue:
520 else:513 if instance['kernel_id']:
521 libvirt_xml = self.libvirt_xml % xml_info514 xml_info['kernel'] = xml_info['basepath'] + "/kernel"
515
516 if instance['ramdisk_id']:
517 xml_info['ramdisk'] = xml_info['basepath'] + "/ramdisk"
518
519 xml_info['disk'] = xml_info['basepath'] + "/disk"
520
521 xml = str(Template(self.libvirt_xml, searchList=[xml_info]))
522 logging.debug('instance %s: finished toXML method', instance['name'])522 logging.debug('instance %s: finished toXML method', instance['name'])
523523
524 return libvirt_xml524 return xml
525525
526 def get_info(self, instance_name):526 def get_info(self, instance_name):
527 try:527 try:
528528
=== modified file 'tools/pip-requires'
--- tools/pip-requires 2010-12-13 17:02:27 +0000
+++ tools/pip-requires 2010-12-20 21:55:17 +0000
@@ -2,6 +2,7 @@
2pep8==0.5.02pep8==0.5.0
3pylint==0.193pylint==0.19
4IPy==0.704IPy==0.70
5Cheetah==2.4.2.1
5M2Crypto==0.20.26M2Crypto==0.20.2
6amqplib==0.6.17amqplib==0.6.1
7anyjson==0.2.48anyjson==0.2.4