Merge pull request #320 from chamerling/master

Security Groups Minimal support for Nove
This commit is contained in:
Andrei Savu 2012-01-17 04:39:32 -08:00
commit 55c12680a3
12 changed files with 599 additions and 49 deletions

View File

@ -341,4 +341,20 @@ public interface NovaAsyncClient {
@ExceptionParser(ReturnNullOnNotFoundOr404.class)
ListenableFuture<FloatingIP> getFloatingIP(@PathParam("id") int id);
@GET
@Unwrap
@Consumes(MediaType.APPLICATION_JSON)
@QueryParams(keys = "format", values = "json")
@Path("/os-security-groups")
@ExceptionParser(ReturnEmptySetOnNotFoundOr404.class)
ListenableFuture<? extends Set<SecurityGroup>> listSecurityGroups();
@GET
@Unwrap
@Consumes(MediaType.APPLICATION_JSON)
@QueryParams(keys = "format", values = "json")
@Path("/os-security-groups/{id}")
@ExceptionParser(ReturnNullOnNotFoundOr404.class)
ListenableFuture<SecurityGroup> getSecurityGroup(@PathParam("id") int id);
}

View File

@ -29,6 +29,7 @@ import org.jclouds.openstack.nova.domain.Flavor;
import org.jclouds.openstack.nova.domain.FloatingIP;
import org.jclouds.openstack.nova.domain.Image;
import org.jclouds.openstack.nova.domain.RebootType;
import org.jclouds.openstack.nova.domain.SecurityGroup;
import org.jclouds.openstack.nova.domain.Server;
import org.jclouds.openstack.nova.options.CreateServerOptions;
import org.jclouds.openstack.nova.options.ListOptions;
@ -313,4 +314,24 @@ public interface NovaClient {
* @return the floating IP or null if not found
*/
FloatingIP getFloatingIP(int id);
/**
* Get all the security groups
*
* @see <a href="http://wiki.openstack.org/os-security-groups">http://wiki.openstack.org/os-security-groups</a>
* @since OpenStack API v1.1
* @return all the security groups for the current tenant
*/
Set<SecurityGroup> listSecurityGroups();
/**
* Get a security group from its ID
*
* @see <a href="http://wiki.openstack.org/os-security-groups">http://wiki.openstack.org/os-security-groups</a>
* @since OpenStack API v1.1
* @param id the ID of the security group to get details from
* @return the security group or null if not found
*/
SecurityGroup getSecurityGroup(int id);
}

View File

@ -60,7 +60,7 @@ public class NovaComputeServiceAdapter implements ComputeServiceAdapter<Server,
public NodeAndInitialCredentials<Server> createNodeWithGroupEncodedIntoName(String group, String name,
Template template) {
Server server = client.createServer(name, template.getImage().getId(), template.getHardware().getId(),
withMetadata(template.getOptions().getUserMetadata()));
withMetadata(template.getOptions().getUserMetadata()).withSecurityGroup(group));
return new NodeAndInitialCredentials<Server>(server, server.getId() + "", LoginCredentials.builder().password(
server.getAdminPass()).build());

View File

@ -0,0 +1,165 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jclouds.openstack.nova.domain;
import com.google.gson.annotations.SerializedName;
/**
* Defines a security group
*
* @author chamerling
*
*/
public class SecurityGroup {
private int id;
private String name;
private String description;
@SerializedName(value="tenant_id")
private String tenantId;
public SecurityGroup() {
}
/**
* @return the id
*/
public int getId() {
return id;
}
/**
* @param id the id to set
*/
public void setId(int id) {
this.id = id;
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the description
*/
public String getDescription() {
return description;
}
/**
* @param description the description to set
*/
public void setDescription(String description) {
this.description = description;
}
/**
* @return the tenantId
*/
public String getTenantId() {
return tenantId;
}
/**
* @param tenantId the tenantId to set
*/
public void setTenantId(String tenantId) {
this.tenantId = tenantId;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("SecurityGroup [id=");
builder.append(id);
builder.append(", name=");
builder.append(name);
builder.append(", description=");
builder.append(description);
builder.append(", tenantId=");
builder.append(tenantId);
builder.append("]");
return builder.toString();
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((description == null) ? 0 : description.hashCode());
result = prime * result + id;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result
+ ((tenantId == null) ? 0 : tenantId.hashCode());
return result;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SecurityGroup other = (SecurityGroup) obj;
if (description == null) {
if (other.description != null)
return false;
} else if (!description.equals(other.description))
return false;
if (id != other.id)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (tenantId == null) {
if (other.tenantId != null)
return false;
} else if (!tenantId.equals(other.tenantId))
return false;
return true;
}
}

View File

@ -18,11 +18,13 @@
*/
package org.jclouds.openstack.nova.domain;
import com.google.common.collect.Maps;
import com.google.gson.annotations.SerializedName;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gson.annotations.SerializedName;
/**
* A server is a virtual machine instance in the OpenStack Nova system. Flavor and image are
@ -49,6 +51,13 @@ public class Server extends Resource {
@SerializedName(value="key_name")
private String keyName;
/**
* Actually, security groups are not returned by nova on server query but is
* needed when creating a server to specify a set of groups
*/
@SerializedName(value="security_groups")
private Set<SecurityGroup> securityGroups = Sets.newHashSet();
private Date created;
private Date updated;
@ -203,6 +212,14 @@ public class Server extends Resource {
public void setKeyName(String keyName) {
this.keyName = keyName;
}
public Set<SecurityGroup> getSecurityGroups() {
return securityGroups;
}
public void setSecurityGroups(Set<SecurityGroup> securityGroups) {
this.securityGroups = securityGroups;
}
@Override
public int hashCode() {
@ -264,6 +281,11 @@ public class Server extends Resource {
return false;
} else if (!metadata.equals(other.metadata))
return false;
if (securityGroups == null) {
if (other.securityGroups != null)
return false;
} else if (!securityGroups.equals(other.securityGroups))
return false;
if (uuid == null) {
if (other.uuid != null)
return false;
@ -300,7 +322,7 @@ public class Server extends Resource {
public String toString() {
return "Server [addresses=" + addresses + ", adminPass=" + adminPass + ", flavorRef="
+ flavorRef + ", hostId=" + hostId + ", id=" + id + ", imageRef=" + imageRef
+ ", metadata=" + metadata + ", uuid=" + uuid + ", name=" + name + ", keyName=" + keyName + "]";
+ ", metadata=" + metadata + ", uuid=" + uuid + ", name=" + name + ", keyName=" + keyName + " , securityGroups=" + securityGroups + "]";
}
}

View File

@ -25,17 +25,21 @@ import static com.google.common.base.Preconditions.checkState;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.inject.Inject;
import org.jclouds.encryption.internal.Base64;
import org.jclouds.http.HttpRequest;
import org.jclouds.openstack.nova.domain.SecurityGroup;
import org.jclouds.rest.MapBinder;
import org.jclouds.rest.binders.BindToJsonPayload;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gson.annotations.SerializedName;
/**
*
@ -77,6 +81,8 @@ public class CreateServerOptions implements MapBinder {
Map<String, String> metadata;
List<File> personality;
String key_name;
@SerializedName(value="security_groups")
Set<SecurityGroup> securityGroups;
private ServerRequest(String name, String imageRef, String flavorRef) {
this.name = name;
@ -88,6 +94,7 @@ public class CreateServerOptions implements MapBinder {
private Map<String, String> metadata = Maps.newHashMap();
private List<File> files = Lists.newArrayList();
private Set<String> securityGroups = Sets.newHashSet();
private String keyName;
@Override
@ -101,6 +108,14 @@ public class CreateServerOptions implements MapBinder {
server.personality = files;
if (keyName != null)
server.key_name = keyName;
if (securityGroups.size() > 0) {
server.securityGroups = Sets.newHashSet();
for (String groupName : securityGroups) {
SecurityGroup group = new SecurityGroup();
group.setName(groupName);
server.securityGroups.add(group);
}
}
return bindToRequest(request, ImmutableMap.of("server", server));
}
@ -161,6 +176,18 @@ public class CreateServerOptions implements MapBinder {
return this;
}
/**
* Defines the security group name to be used when creating a server.
*
* @param groupName
* @return
*/
public CreateServerOptions withSecurityGroup(String groupName) {
checkNotNull(groupName, "groupName");
this.securityGroups.add(groupName);
return this;
}
public static class Builder {
/**
@ -186,6 +213,14 @@ public class CreateServerOptions implements MapBinder {
CreateServerOptions options = new CreateServerOptions();
return options.withKeyName(keyName);
}
/**
* @see CreateServerOptions#withGroupName(String)
*/
public static CreateServerOptions withSecurityGroup(String name) {
CreateServerOptions options = new CreateServerOptions();
return options.withSecurityGroup(name);
}
}
@Override

View File

@ -18,48 +18,49 @@
*/
package org.jclouds.openstack.nova;
import com.google.common.collect.ImmutableMap;
import com.google.inject.Module;
import com.google.inject.TypeLiteral;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.RequiresHttp;
import org.jclouds.http.functions.ReleasePayloadAndReturn;
import org.jclouds.http.functions.ReturnTrueIf2xx;
import org.jclouds.http.functions.UnwrapOnlyJsonValue;
import org.jclouds.openstack.OpenStackAuthAsyncClient.AuthenticationResponse;
import org.jclouds.openstack.TestOpenStackAuthenticationModule;
import org.jclouds.openstack.filters.AddTimestampQuery;
import org.jclouds.openstack.filters.AuthenticateRequest;
import org.jclouds.openstack.nova.config.NovaRestClientModule;
import org.jclouds.openstack.nova.domain.RebootType;
import org.jclouds.openstack.nova.options.CreateServerOptions;
import org.jclouds.openstack.nova.options.ListOptions;
import org.jclouds.openstack.nova.options.RebuildServerOptions;
import org.jclouds.rest.ConfiguresRestClient;
import org.jclouds.rest.RestClientTest;
import org.jclouds.rest.RestContextFactory;
import org.jclouds.rest.RestContextSpec;
import org.jclouds.rest.functions.ReturnEmptySetOnNotFoundOr404;
import org.jclouds.rest.functions.ReturnFalseOnNotFoundOr404;
import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404;
import org.jclouds.rest.internal.RestAnnotationProcessor;
import org.testng.annotations.Test;
import javax.ws.rs.core.MediaType;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Date;
import java.util.Properties;
import static org.jclouds.Constants.PROPERTY_API_VERSION;
import static org.jclouds.openstack.nova.options.CreateServerOptions.Builder.withFile;
import static org.jclouds.openstack.nova.options.CreateServerOptions.Builder.withMetadata;
import static org.jclouds.openstack.nova.options.ListOptions.Builder.changesSince;
import static org.jclouds.openstack.nova.options.ListOptions.Builder.withDetails;
import static org.jclouds.openstack.nova.options.RebuildServerOptions.Builder.withImage;
//import static org.junit.Assert.*;
import static org.testng.Assert.assertEquals;
import static org.jclouds.Constants.PROPERTY_API_VERSION;
import static org.jclouds.openstack.nova.options.CreateServerOptions.Builder.withFile;
import static org.jclouds.openstack.nova.options.CreateServerOptions.Builder.withMetadata;
import static org.jclouds.openstack.nova.options.ListOptions.Builder.changesSince;
import static org.jclouds.openstack.nova.options.ListOptions.Builder.withDetails;
import static org.jclouds.openstack.nova.options.RebuildServerOptions.Builder.withImage;
import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Date;
import java.util.Properties;
import javax.ws.rs.core.MediaType;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.RequiresHttp;
import org.jclouds.http.functions.ReleasePayloadAndReturn;
import org.jclouds.http.functions.ReturnTrueIf2xx;
import org.jclouds.http.functions.UnwrapOnlyJsonValue;
import org.jclouds.openstack.OpenStackAuthAsyncClient.AuthenticationResponse;
import org.jclouds.openstack.TestOpenStackAuthenticationModule;
import org.jclouds.openstack.filters.AddTimestampQuery;
import org.jclouds.openstack.filters.AuthenticateRequest;
import org.jclouds.openstack.nova.config.NovaRestClientModule;
import org.jclouds.openstack.nova.domain.RebootType;
import org.jclouds.openstack.nova.options.CreateServerOptions;
import org.jclouds.openstack.nova.options.ListOptions;
import org.jclouds.openstack.nova.options.RebuildServerOptions;
import org.jclouds.rest.ConfiguresRestClient;
import org.jclouds.rest.RestClientTest;
import org.jclouds.rest.RestContextFactory;
import org.jclouds.rest.RestContextSpec;
import org.jclouds.rest.functions.ReturnEmptySetOnNotFoundOr404;
import org.jclouds.rest.functions.ReturnFalseOnNotFoundOr404;
import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404;
import org.jclouds.rest.internal.RestAnnotationProcessor;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMap;
import com.google.inject.Module;
import com.google.inject.TypeLiteral;
/**
* Tests behavior of {@code NovaAsyncClient}
@ -153,8 +154,8 @@ public class NovaAsyncClientTest extends RestClientTest<NovaAsyncClient> {
assertExceptionParserClassEquals(method, null);
checkFilters(request);
}
}
public void testDeleteImage() throws IOException, SecurityException, NoSuchMethodException {
Method method = NovaAsyncClient.class.getMethod("deleteImage", int.class);
HttpRequest request = processor.createRequest(method, 2);
@ -693,6 +694,42 @@ public class NovaAsyncClientTest extends RestClientTest<NovaAsyncClient> {
checkFilters(request);
}
public void testGetSecurityGroup() throws SecurityException,
NoSuchMethodException {
Method method = NovaAsyncClient.class.getMethod("getSecurityGroup",
int.class);
HttpRequest request = processor.createRequest(method, 2);
assertRequestLineEquals(request,
"GET http://endpoint/vapi-version/os-security-groups/2?format=json HTTP/1.1");
assertNonPayloadHeadersEqual(request, "Accept: application/json\n");
assertPayloadEquals(request, null, null, false);
assertResponseParserClassEquals(method, request,
UnwrapOnlyJsonValue.class);
assertSaxResponseParserClassEquals(method, null);
assertExceptionParserClassEquals(method,
ReturnNullOnNotFoundOr404.class);
checkFilters(request);
}
public void testListSecurityGroups() throws SecurityException, NoSuchMethodException {
Method method = NovaAsyncClient.class.getMethod("listSecurityGroups");
HttpRequest request = processor.createRequest(method);
assertRequestLineEquals(request,
"GET http://endpoint/vapi-version/os-security-groups?format=json HTTP/1.1");
assertNonPayloadHeadersEqual(request, "Accept: application/json\n");
assertPayloadEquals(request, null, null, false);
assertResponseParserClassEquals(method, request, UnwrapOnlyJsonValue.class);
assertSaxResponseParserClassEquals(method, null);
assertExceptionParserClassEquals(method, ReturnEmptySetOnNotFoundOr404.class);
checkFilters(request);
}
@Override
protected TypeLiteral<RestAnnotationProcessor<NovaAsyncClient>> createTypeLiteral() {

View File

@ -0,0 +1,74 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jclouds.openstack.nova.functions;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import java.io.IOException;
import java.io.InputStream;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.functions.UnwrapOnlyJsonValue;
import org.jclouds.io.Payloads;
import org.jclouds.json.config.GsonModule;
import org.jclouds.openstack.nova.domain.SecurityGroup;
import org.testng.annotations.Test;
import com.google.gson.Gson;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
/**
* @author chamerling
*
*/
@Test(groups = "unit")
public class ParseSecurityGroupFromJsonResponse {
@Test
public void testParseSecurityGroupFromJsonResponseTest() throws IOException {
SecurityGroup response = parseSecurityGroup();
String json = new Gson().toJson(response);
assertNotNull(json);
assertNotNull(response);
assertEquals(response.getId(), 0);
assertEquals(response.getName(), "name0");
assertEquals(response.getDescription(), "description0");
assertEquals(response.getTenantId(), "tenant0");
}
public static SecurityGroup parseSecurityGroup() {
Injector i = Guice.createInjector(new GsonModule());
InputStream is = ParseFloatingIPFromJsonResponse.class
.getResourceAsStream("/test_get_security_group.json");
UnwrapOnlyJsonValue<SecurityGroup> parser = i.getInstance(Key
.get(new TypeLiteral<UnwrapOnlyJsonValue<SecurityGroup>>() {
}));
return parser.apply(new HttpResponse(200, "ok", Payloads
.newInputStreamPayload(is)));
}
}

View File

@ -0,0 +1,76 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jclouds.openstack.nova.functions;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.functions.UnwrapOnlyJsonValue;
import org.jclouds.io.Payloads;
import org.jclouds.json.config.GsonModule;
import org.jclouds.openstack.nova.domain.SecurityGroup;
import org.testng.annotations.Test;
import com.google.gson.Gson;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
/**
* @author chamerling
*
*/
@Test(groups = "unit")
public class ParseSecurityGroupsListFromJsonResponse {
Injector i = Guice.createInjector(new GsonModule());
@Test
public void testParseSecurityGroupsFromJsonResponseTest() throws IOException {
List<SecurityGroup> response = parseSecurityGroups();
String json = new Gson().toJson(response);
assertNotNull(json);
assertEquals(response.size(), 1);
assertEquals(response.get(0).getId(), 1);
assertEquals(response.get(0).getName(), "name1");
assertEquals(response.get(0).getDescription(), "description1");
assertEquals(response.get(0).getTenantId(), "tenant1");
}
public static List<SecurityGroup> parseSecurityGroups() {
Injector i = Guice.createInjector(new GsonModule());
InputStream is = ParseFloatingIPFromJsonResponse.class
.getResourceAsStream("/test_list_security_groups.json");
UnwrapOnlyJsonValue<List<SecurityGroup>> parser = i.getInstance(Key
.get(new TypeLiteral<UnwrapOnlyJsonValue<List<SecurityGroup>>>() {
}));
return parser.apply(new HttpResponse(200, "ok", Payloads
.newInputStreamPayload(is)));
}
}

View File

@ -29,6 +29,7 @@ import javax.ws.rs.HttpMethod;
import java.net.URI;
import static org.jclouds.openstack.nova.options.CreateServerOptions.Builder.withFile;
import static org.jclouds.openstack.nova.options.CreateServerOptions.Builder.withSecurityGroup;
import static org.testng.Assert.assertEquals;
/**
@ -69,6 +70,24 @@ public class CreateServerOptionsTest {
HttpRequest request = buildRequest(options);
assertFile(request);
}
@Test
public void testWithSecurityGroup() {
CreateServerOptions options = withSecurityGroup("mygroup");
HttpRequest request = buildRequest(options);
assertEquals(
request.getPayload().getRawContent(),
"{\"server\":{\"name\":\"foo\",\"imageRef\":\"1\",\"flavorRef\":\"2\",\"security_groups\":[{\"id\":0,\"name\":\"mygroup\"}]}}");
}
@Test
public void testWithSecurityGroups() {
CreateServerOptions options = withSecurityGroup("mygroup").withSecurityGroup("myothergroup");
HttpRequest request = buildRequest(options);
assertEquals(
request.getPayload().getRawContent(),
"{\"server\":{\"name\":\"foo\",\"imageRef\":\"1\",\"flavorRef\":\"2\",\"security_groups\":[{\"id\":0,\"name\":\"myothergroup\"},{\"id\":0,\"name\":\"mygroup\"}]}}");
}
private void assertFile(HttpRequest request) {
assertEquals(request.getPayload().getRawContent(),

View File

@ -0,0 +1,34 @@
{
"security_group":
{
"rules": [
{
"from_port": 22,
"group": {},
"ip_protocol": "tcp",
"to_port": 22,
"parent_group_id": 28,
"ip_range": {
"cidr": "10.2.6.0/24"
},
"id": 108
},
{
"from_port": 22,
"group": {
"tenant_id": "admin",
"name": "11111"
},
"ip_protocol": "tcp",
"to_port": 22,
"parent_group_id": 28,
"ip_range": {},
"id": 109
}
],
"tenant_id": "tenant0",
"id": 0,
"name": "name0",
"description": "description0"
}
}

View File

@ -0,0 +1,51 @@
{
"security_groups":[
{
"rules":[
{
"from_port":22,
"group":{
},
"ip_protocol":"tcp",
"to_port":22,
"parent_group_id":3,
"ip_range":{
"cidr":"0.0.0.0/0"
},
"id":107
},
{
"from_port":7600,
"group":{
},
"ip_protocol":"tcp",
"to_port":7600,
"parent_group_id":3,
"ip_range":{
"cidr":"0.0.0.0/0"
},
"id":118
},
{
"from_port":8084,
"group":{
},
"ip_protocol":"tcp",
"to_port":8084,
"parent_group_id":3,
"ip_range":{
"cidr":"0.0.0.0/0"
},
"id":119
}
],
"tenant_id":"tenant1",
"id":1,
"name":"name1",
"description":"description1"
}
]
}