Implement shutdown bundle that stops system bundle on JMX, netconf RPC, or when invoked on
shutdown service taken from OSGi service registry. User must provide a shutdown secret
set during initial module configuration and may provide a reason of shutdown.
Currently this bundle is not instanciated during server startup.
Change-Id: I4ca652265fc676c9f387c7caa49bca499abd4400
Signed-off-by: Tomas Olvecky <tolvecky@cisco.com>
<groupId>org.opendaylight.yangtools</groupId>
<artifactId>yang-maven-plugin</artifactId>
</plugin>
- <plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>build-helper-maven-plugin</artifactId>
- </plugin>
</plugins>
</build>
</project>
public class ValidationException extends RuntimeException {
private static final long serialVersionUID = -6072893219820274247L;
- private final Map<String, Map<String, ExceptionMessageWithStackTrace>> failedValidations;
+ private final Map<String/* module name */, Map<String/* instance name */, ExceptionMessageWithStackTrace>> failedValidations;
public ValidationException(
Map<String /* module name */, Map<String /* instance name */, ExceptionMessageWithStackTrace>> failedValidations) {
return new ValidationException(failedValidations);
}
- public Map<String, Map<String, ExceptionMessageWithStackTrace>> getFailedValidations() {
+ public Map<String/* module name */, Map<String/* instance name */, ExceptionMessageWithStackTrace>> getFailedValidations() {
return failedValidations;
}
import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
+import javax.management.RuntimeMBeanException;
import java.io.Closeable;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
import java.util.Dictionary;
import java.util.List;
import java.util.Set;
}
}
}
+
+ /**
+ * Expand inner exception wrapped by JMX
+ *
+ * @param innerObject jmx proxy which will be wrapped and returned
+ */
+ protected <T> T rethrowCause(final T innerObject) {
+
+ Object proxy = Proxy.newProxyInstance(innerObject.getClass().getClassLoader(),
+ innerObject.getClass().getInterfaces(), new InvocationHandler() {
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ try {
+ return method.invoke(innerObject, args);
+ } catch (InvocationTargetException e) {
+ try {
+ throw e.getTargetException();
+ } catch (RuntimeMBeanException e2) {
+ throw e2.getTargetException();
+ }
+ }
+ }
+ });
+ return (T) proxy;
+ }
+
}
<configuration>
<sources>
<source>${jmxGeneratorPath}</source>
+ <source>${salGeneratorPath}</source>
</sources>
</configuration>
</execution>
<module>config-persister-directory-adapter</module>
<module>config-persister-directory-xml-adapter</module>
<module>yang-test-plugin</module>
+ <module>shutdown-api</module>
+ <module>shutdown-impl</module>
</modules>
<profiles>
<opendaylight.yang.version>0.5.9-SNAPSHOT</opendaylight.yang.version>
<opendaylight.binding.version>0.6.0-SNAPSHOT</opendaylight.binding.version>
<opendaylight.yangtools.version>0.1.1-SNAPSHOT</opendaylight.yangtools.version>
+ <salGeneratorPath>${project.build.directory}/generated-sources/sal</salGeneratorPath>
</properties>
<dependencies>
<version>${config.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>shutdown-api</artifactId>
+ <version>${config.version}</version>
+ </dependency>
+
<!-- MD-SAL -->
<dependency>
<groupId>org.opendaylight.yangtools</groupId>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
- <plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>build-helper-maven-plugin</artifactId>
- </plugin>
+
+
</plugins>
<pluginManagement>
org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl
</codeGeneratorClass>
<outputBaseDir>
- target/generated-sources/sal
+ ${salGeneratorPath}
</outputBaseDir>
</generator>
<generator>
</dependency>
</dependencies>
</plugin>
+ <!-- tell eclipse about generated source folders -->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <version>1.8</version>
+ <executions>
+ <execution>
+ <id>add-source</id>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>add-source</goal>
+ </goals>
+ <configuration>
+ <sources>
+ <source>${salGeneratorPath}</source>
+ </sources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>config-plugin-parent</artifactId>
+ <groupId>org.opendaylight.controller</groupId>
+ <version>0.2.3-SNAPSHOT</version>
+ <relativePath>../config-plugin-parent</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <packaging>bundle</packaging>
+ <artifactId>shutdown-api</artifactId>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>config-api</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Export-Package>
+ org.opendaylight.controller.config.shutdown,
+ org.opendaylight.controller.config.yang.shutdown
+ </Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-maven-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.shutdown;
+
+import com.google.common.base.Optional;
+
+public interface ShutdownService {
+
+ /**
+ * Shut down the server.
+ *
+ * @param inputSecret must match configured secret of the implementation
+ * @param reason Optional string to be logged while shutting down
+ */
+ void shutdown(String inputSecret, Optional<String> reason);
+}
--- /dev/null
+module shutdown {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:controller:shutdown";
+ prefix "shutdown";
+
+ import config { prefix config; revision-date 2013-04-05; }
+
+ description
+ "This module contains the base YANG definitions for
+ shutdown service.
+
+ Copyright (c)2013 Cisco Systems, Inc. All rights reserved.;
+
+ This program and the accompanying materials are made available
+ under the terms of the Eclipse Public License v1.0 which
+ accompanies this distribution, and is available at
+ http://www.eclipse.org/legal/epl-v10.html";
+
+ revision "2013-12-18" {
+ description
+ "Initial revision.";
+ }
+
+ identity shutdown {
+ base "config:service-type";
+ config:java-class "org.opendaylight.controller.config.shutdown.ShutdownService";
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>config-plugin-parent</artifactId>
+ <groupId>org.opendaylight.controller</groupId>
+ <version>0.2.3-SNAPSHOT</version>
+ <relativePath>../config-plugin-parent</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <packaging>bundle</packaging>
+ <artifactId>shutdown-impl</artifactId>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>config-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>shutdown-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>config-manager</artifactId>
+ <scope>test</scope>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>config-manager</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>config-util</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.bgpcep</groupId>
+ <artifactId>mockito-configuration</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-maven-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--- /dev/null
+/**
+ * Generated file
+
+ * Generated from: yang module name: shutdown-impl yang module local name: shutdown
+ * Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator
+ * Generated at: Wed Dec 18 14:02:06 CET 2013
+ *
+ * Do not modify this file unless it is present under src/main directory
+ */
+package org.opendaylight.controller.config.yang.shutdown.impl;
+
+import org.opendaylight.controller.config.api.DependencyResolver;
+import org.opendaylight.controller.config.api.JmxAttributeValidationException;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.osgi.framework.Bundle;
+
+public final class ShutdownModule extends AbstractShutdownModule {
+ private final Bundle systemBundle;
+ private final ShutdownModule nullableOldModule;
+
+ public ShutdownModule(ModuleIdentifier identifier, Bundle systemBundle) {
+ super(identifier, null);
+ singletonCheck(identifier);
+ this.systemBundle = systemBundle;
+ this.nullableOldModule = null;
+ }
+
+ public ShutdownModule(ModuleIdentifier identifier, ShutdownModule oldModule, java.lang.AutoCloseable oldInstance,
+ Bundle systemBundle) {
+ super(identifier, null, oldModule, oldInstance);
+ singletonCheck(identifier);
+ this.systemBundle = systemBundle;
+ this.nullableOldModule = oldModule;
+ }
+
+ private static void singletonCheck(ModuleIdentifier identifier) {
+ if (AbstractShutdownModuleFactory.NAME.equals(identifier.getInstanceName()) == false) {
+ throw new IllegalArgumentException("Singleton enforcement failed. Expected instance name " + AbstractShutdownModuleFactory.NAME);
+ }
+ }
+
+ @Deprecated // needed for generated code
+ public ShutdownModule(ModuleIdentifier identifier, DependencyResolver dependencyResolver, ShutdownModule oldModule,
+ AutoCloseable oldInstance) {
+ super(identifier, dependencyResolver, oldModule, oldInstance);
+ throw new UnsupportedOperationException();
+ }
+
+ @Deprecated // needed for generated code
+ public ShutdownModule(ModuleIdentifier identifier, DependencyResolver dependencyResolver) {
+ super(identifier, dependencyResolver);
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getSecret() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getOldSecret() {
+ throw new UnsupportedOperationException();
+ }
+
+ String getActualSecret() {
+ return super.getSecret();
+ }
+
+ String getActualOldSecret() {
+ return super.getOldSecret();
+ }
+
+ @Override
+ protected void customValidation() {
+ JmxAttributeValidationException.checkNotNull(super.getOldSecret(), oldSecretJmxAttribute);
+ JmxAttributeValidationException.checkNotNull(super.getSecret(), secretJmxAttribute);
+ if (nullableOldModule != null) {
+ // if nothing changed, remain valid
+ boolean sameAsOldModule = isSame(nullableOldModule);
+ if (sameAsOldModule == false) {
+ boolean valid = getActualOldSecret().equals(nullableOldModule.getActualSecret());
+ JmxAttributeValidationException.checkCondition(valid, "Invalid old secret", oldSecretJmxAttribute);
+ }
+ }
+ }
+
+ @Override
+ public java.lang.AutoCloseable createInstance() {
+ return new ShutdownServiceImpl(getActualSecret(), systemBundle, getRootRuntimeBeanRegistratorWrapper());
+ }
+}
--- /dev/null
+/**
+ * Generated file
+
+ * Generated from: yang module name: shutdown-impl yang module local name: shutdown
+ * Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator
+ * Generated at: Wed Dec 18 14:02:06 CET 2013
+ *
+ * Do not modify this file unless it is present under src/main directory
+ */
+package org.opendaylight.controller.config.yang.shutdown.impl;
+
+import org.opendaylight.controller.config.api.DependencyResolver;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+public class ShutdownModuleFactory extends AbstractShutdownModuleFactory {
+
+ @Override
+ public org.opendaylight.controller.config.spi.Module createModule(String instanceName, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver, org.opendaylight.controller.config.api.DynamicMBeanWithInstance old, org.osgi.framework.BundleContext bundleContext) throws Exception {
+ org.opendaylight.controller.config.yang.shutdown.impl.ShutdownModule oldModule = null;
+ try {
+ oldModule = (org.opendaylight.controller.config.yang.shutdown.impl.ShutdownModule) old.getModule();
+ } catch(Exception e) {
+ return handleChangedClass(old);
+ }
+ org.opendaylight.controller.config.yang.shutdown.impl.ShutdownModule module = instantiateModule(instanceName, dependencyResolver, oldModule, old.getInstance(), bundleContext);
+
+ module.setOldSecret(oldModule.getActualOldSecret());
+ module.setSecret(oldModule.getActualSecret());
+
+ return module;
+ }
+
+
+ public ShutdownModule instantiateModule(String instanceName, DependencyResolver dependencyResolver,
+ ShutdownModule oldModule, AutoCloseable oldInstance,
+ BundleContext bundleContext) {
+ Bundle systemBundle = bundleContext.getBundle(0);
+ return new ShutdownModule(new ModuleIdentifier(NAME, instanceName), oldModule, oldInstance, systemBundle);
+ }
+
+
+ public ShutdownModule instantiateModule(String instanceName, DependencyResolver dependencyResolver,
+ BundleContext bundleContext) {
+ Bundle systemBundle = bundleContext.getBundle(0);
+ return new ShutdownModule(new ModuleIdentifier(NAME, instanceName), systemBundle);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.yang.shutdown.impl;
+
+import com.google.common.base.Optional;
+import org.opendaylight.controller.config.shutdown.ShutdownService;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ShutdownServiceImpl implements ShutdownService, AutoCloseable {
+ private final ShutdownService impl;
+ private final ShutdownRuntimeRegistration registration;
+
+ public ShutdownServiceImpl(String secret, Bundle systemBundle,
+ ShutdownRuntimeRegistrator rootRuntimeBeanRegistratorWrapper) {
+ if (secret == null) {
+ throw new IllegalArgumentException("Secret cannot be null");
+ }
+ impl = new Impl(secret, systemBundle);
+ registration = rootRuntimeBeanRegistratorWrapper.register(new MXBeanImpl(impl));
+ }
+
+ @Override
+ public void shutdown(String inputSecret, Optional<String> reason) {
+ impl.shutdown(inputSecret, reason);
+ }
+
+ @Override
+ public void close() {
+ registration.close();
+ }
+}
+
+class Impl implements ShutdownService {
+ private static final Logger logger = LoggerFactory.getLogger(Impl.class);
+ private final String secret;
+ private final Bundle systemBundle;
+
+ Impl(String secret, Bundle systemBundle) {
+ this.secret = secret;
+ this.systemBundle = systemBundle;
+ }
+
+ @Override
+ public void shutdown(String inputSecret, Optional<String> reason) {
+ logger.warn("Shutdown issued with secret {} and reason {}", inputSecret, reason);
+ try {
+ Thread.sleep(1000); // prevent brute force attack
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ logger.warn("Shutdown process interrupted", e);
+ }
+ if (this.secret.equals(inputSecret)) {
+ logger.info("Server is shutting down");
+
+ Thread stopSystemBundle = new Thread() {
+ @Override
+ public void run() {
+ try {
+ // wait so that JMX response is received
+ Thread.sleep(1000);
+ systemBundle.stop();
+ } catch (BundleException e) {
+ logger.warn("Can not stop OSGi server", e);
+ } catch (InterruptedException e) {
+ logger.warn("Shutdown process interrupted", e);
+ }
+ }
+ };
+ stopSystemBundle.start();
+
+ } else {
+ logger.warn("Unauthorized attempt to shut down server");
+ throw new IllegalArgumentException("Invalid secret");
+ }
+ }
+
+}
+
+class MXBeanImpl implements ShutdownRuntimeMXBean {
+ private final ShutdownService impl;
+
+ MXBeanImpl(ShutdownService impl) {
+ this.impl = impl;
+ }
+
+ @Override
+ public void shutdown(String inputSecret, String nullableReason) {
+ Optional<String> optionalReason;
+ if (nullableReason == null) {
+ optionalReason = Optional.absent();
+ } else {
+ optionalReason = Optional.of(nullableReason);
+ }
+ impl.shutdown(inputSecret, optionalReason);
+ }
+}
--- /dev/null
+module shutdown-impl {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:controller:shutdown:impl";
+ prefix "shutdown-impl";
+
+ import shutdown { prefix shutdown; revision-date 2013-12-18; }
+ import config { prefix config; revision-date 2013-04-05; }
+ import rpc-context { prefix rpcx; revision-date 2013-06-17; }
+
+ organization "Cisco Systems, Inc.";
+
+ description
+ "This module contains the base YANG definitions for
+ shutdown implementation.
+
+ Copyright (c)2013 Cisco Systems, Inc. All rights reserved.;
+
+ This program and the accompanying materials are made available
+ under the terms of the Eclipse Public License v1.0 which
+ accompanies this distribution, and is available at
+ http://www.eclipse.org/legal/epl-v10.html";
+
+ revision "2013-12-18" {
+ description
+ "Initial revision.";
+ }
+
+ identity shutdown {
+ base config:module-type;
+ config:provided-service shutdown:shutdown;
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case shutdown {
+ when "/config:modules/config:module/config:type = 'shutdown'";
+ leaf secret {
+ type string;
+ default "";
+ }
+ leaf old-secret {
+ type string;
+ default "";
+ }
+ }
+ }
+
+ augment "/config:modules/config:module/config:state" {
+ case shutdown {
+ when "/config:modules/config:module/config:type = 'shutdown'";
+ rpcx:rpc-context-instance "shutdown-rpc";
+ }
+ }
+
+ identity shutdown-rpc;
+
+ rpc shutdown {
+ input {
+ uses rpcx:rpc-context-ref {
+ refine context-instance {
+ rpcx:rpc-context-instance shutdown-rpc;
+ }
+ }
+ leaf input-secret {
+ type string;
+ }
+ leaf reason {
+ type string;
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.yang.shutdown.impl;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.controller.config.api.ValidationException;
+import org.opendaylight.controller.config.api.ValidationException.ExceptionMessageWithStackTrace;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.manager.impl.AbstractConfigTest;
+import org.opendaylight.controller.config.manager.impl.factoriesresolver.HardcodedModuleFactoriesResolver;
+import org.opendaylight.controller.config.manager.impl.factoriesresolver.ModuleFactoriesResolver;
+import org.opendaylight.controller.config.util.ConfigTransactionJMXClient;
+import org.osgi.framework.Bundle;
+
+import javax.management.JMX;
+import javax.management.ObjectName;
+import java.util.Collections;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.opendaylight.controller.config.yang.shutdown.impl.ShutdownModuleFactory.NAME;
+
+public class ShutdownTest extends AbstractConfigTest {
+ private final ShutdownModuleFactory factory = new ShutdownModuleFactory();
+ @Mock
+ private Bundle mockedSysBundle;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ ModuleFactoriesResolver factoriesResolver = new HardcodedModuleFactoriesResolver(mockedContext, factory);
+ super.initConfigTransactionManagerImpl(factoriesResolver);
+ doReturn(mockedSysBundle).when(mockedContext).getBundle(0);
+ mockedContext.getBundle(0);
+ doNothing().when(mockedSysBundle).stop();
+ }
+
+ @Test
+ public void testSingleton_invalidName() throws Exception {
+ ConfigTransactionJMXClient transaction = configRegistryClient.createTransaction();
+ try {
+ transaction.createModule(NAME, "foo");
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertEquals("Singleton enforcement failed. Expected instance name shutdown", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testWithoutSecret() throws Exception {
+ ConfigTransactionJMXClient transaction = configRegistryClient.createTransaction();
+ transaction.createModule(NAME, NAME);
+ transaction.commit();
+ // test JMX rpc
+ ObjectName runtimeON = ObjectNameUtil.createRuntimeBeanName(NAME, NAME, Collections.<String, String>emptyMap());
+ ShutdownRuntimeMXBean runtime = configRegistryClient.newMXBeanProxy(runtimeON, ShutdownRuntimeMXBean.class);
+ try {
+ runtime.shutdown("foo", null);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertEquals("Invalid secret", e.getMessage());
+ }
+ runtime.shutdown("", null);
+ assertStopped();
+ }
+
+
+ @Test
+ public void testWithSecret() throws Exception {
+ ConfigTransactionJMXClient transaction = configRegistryClient.createTransaction();
+ ObjectName on = transaction.createModule(NAME, NAME);
+ ShutdownModuleMXBean proxy = transaction.newMXBeanProxy(on, ShutdownModuleMXBean.class);
+ String secret = "secret";
+ proxy.setSecret(secret);
+ transaction.commit();
+ shutdownViaRuntimeJMX(secret);
+
+ // test old secret
+ transaction = configRegistryClient.createTransaction();
+ on = transaction.lookupConfigBean(NAME, NAME);
+ proxy = transaction.newMXBeanProxy(on, ShutdownModuleMXBean.class);
+ try {
+ rethrowCause(proxy).getOldSecret();
+ fail();
+ } catch (UnsupportedOperationException e) {
+ }
+ try {
+ rethrowCause(proxy).getSecret();
+ fail();
+ } catch (UnsupportedOperationException e) {
+ }
+ // set secret to nothing
+ String newSecret = "newSecret";
+ proxy.setSecret(newSecret);
+ try {
+ transaction.commit();
+ fail("Old secret not provided - should fail validation");
+ } catch (ValidationException e) {
+ Map<String, Map<String, ExceptionMessageWithStackTrace>> failedValidations = e.getFailedValidations();
+ assertTrue(failedValidations.containsKey(NAME));
+ ExceptionMessageWithStackTrace exceptionMessageWithStackTrace = failedValidations.get(NAME).get(NAME);
+ assertNotNull(exceptionMessageWithStackTrace);
+ assertEquals("OldSecret Invalid old secret", exceptionMessageWithStackTrace.getMessage());
+
+ }
+ proxy.setOldSecret(secret);
+ transaction.commit();
+ shutdownViaRuntimeJMX(newSecret);
+ }
+
+ private void shutdownViaRuntimeJMX(String secret) throws Exception {
+ // test JMX rpc
+ ObjectName runtimeON = ObjectNameUtil.createRuntimeBeanName(NAME, NAME, Collections.<String, String>emptyMap());
+ ShutdownRuntimeMXBean runtime = JMX.newMXBeanProxy(platformMBeanServer, runtimeON, ShutdownRuntimeMXBean.class);
+ try {
+ runtime.shutdown("", null);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertEquals("Invalid secret", e.getMessage());
+ }
+ runtime.shutdown(secret, null);
+ assertStopped();
+ }
+
+
+ private void assertStopped() throws Exception {
+ Thread.sleep(2000); // happens on another thread
+ verify(mockedSysBundle).stop();
+ verifyNoMoreInteractions(mockedSysBundle);
+ reset(mockedSysBundle);
+ doNothing().when(mockedSysBundle).stop();
+ }
+}
for (RpcDefinition rpc : currentModule.getRpcs()) {
ContainerSchemaNode input = rpc.getInput();
- for (UsesNode uses : input.getUses()) {
-
- if (uses.getGroupingPath().getPath().size() != 1)
- continue;
-
- // check grouping path
- QName qname = uses.getGroupingPath().getPath().get(0);
- if (false == qname
- .equals(ConfigConstants.RPC_CONTEXT_REF_GROUPING_QNAME))
- continue;
-
- for (SchemaNode refinedNode : uses.getRefines().values()) {
-
- for (UnknownSchemaNode unknownSchemaNode : refinedNode
- .getUnknownSchemaNodes()) {
- if (ConfigConstants.RPC_CONTEXT_INSTANCE_EXTENSION_QNAME
- .equals(unknownSchemaNode.getNodeType())) {
- String localIdentityName = unknownSchemaNode
- .getNodeParameter();
- QName identityQName = new QName(
- currentModule.getNamespace(),
- currentModule.getRevision(),
- localIdentityName);
- Set<RpcDefinition> rpcDefinitions = result
- .get(identityQName);
- if (rpcDefinitions == null) {
- throw new IllegalArgumentException(
- "Identity referenced by rpc not found. Identity:"
- + localIdentityName + " , rpc "
- + rpc);
+ if (input != null) {
+ for (UsesNode uses : input.getUses()) {
+
+ if (uses.getGroupingPath().getPath().size() != 1)
+ continue;
+
+ // check grouping path
+ QName qname = uses.getGroupingPath().getPath().get(0);
+ if (false == qname
+ .equals(ConfigConstants.RPC_CONTEXT_REF_GROUPING_QNAME))
+ continue;
+
+ for (SchemaNode refinedNode : uses.getRefines().values()) {
+
+ for (UnknownSchemaNode unknownSchemaNode : refinedNode
+ .getUnknownSchemaNodes()) {
+ if (ConfigConstants.RPC_CONTEXT_INSTANCE_EXTENSION_QNAME
+ .equals(unknownSchemaNode.getNodeType())) {
+ String localIdentityName = unknownSchemaNode
+ .getNodeParameter();
+ QName identityQName = new QName(
+ currentModule.getNamespace(),
+ currentModule.getRevision(),
+ localIdentityName);
+ Set<RpcDefinition> rpcDefinitions = result
+ .get(identityQName);
+ if (rpcDefinitions == null) {
+ throw new IllegalArgumentException(
+ "Identity referenced by rpc not found. Identity:"
+ + localIdentityName + " , rpc "
+ + rpc);
+ }
+ rpcDefinitions.add(rpc);
}
- rpcDefinitions.add(rpc);
}
}
-
}
}
}
+++ /dev/null
-
-#24
-<rpc message-id="101" xm
-#28
-lns="urn:ietf:params:xml:ns:
-#2
-ne
-#33
-tconf:base:1.0">
- <my-own-method
-#3
- xm
-#13
-lns="http://e
-#34
-xample.net/me/my-own/1.0">
- <my
-#8
--first-p
-#21
-arameter>14</my-first
-#26
--parameter>
- <another-p
-#23
-arameter>fred</another-
-#31
-parameter>
- </my-own-method>
- <
-#2
-/r
-#3
-pc>
-##
+++ /dev/null
-
-#22
-<rpc message-id="101"
-#24
-xmlns="urn:ietf:params:x
-#15
-ml:ns:netconf:b
-#54
-ase:1.0">
- <get-config>
- <source>
- <r
-#2
-un
-#9
-ning/>
-
-#18
- </source> <fi
-#33
-lter type="subtree">
- <top x
-#4
-mlns
-#31
-="http://example.com/schema/1.2
-#15
-/config">
-
-#19
- <users>
-
-#8
- <us
-#3
-er/
-#5
->
-
-#77
- </users>
- </top>
- </filter>
- </g
-#17
-et-config>
-</rpc>
-##
+++ /dev/null
-
-#43
-<rpc message-id="101" xmlns="urn:ietf:param
-#14
-s:xml:ns:netco
-#14
-nf:base:1.0">
-
-#26
-<get-config>
- <source>
-
-#35
- <running/>
- </source>
-
-#39
- <filter type="subtree">
- <
-#40
-top xmlns="http://example.com/schema/1.2
-#26
-/config">
- <users>
-
-#36
- <user>
- <name>f
-#56
-red</name>
- </user>
- </users>
-
-#28
- </top>
- </fil
-#1
-t
-#28
-er>
- </get-config>
- </rpc>
-##
+++ /dev/null
-
-#17
-<rpc message-id="
-#25
-101" xmlns="urn:ietf:para
-#19
-ms:xml:ns:netconf:b
-#3
-ase
-#19
-:1.0">
- <get-confi
-#61
-g>
- <source>
- <running/>
- </source>
-
-#43
- <filter type="subtree">
- <!-- requ
-#13
-est a text ve
-#22
-rsion of the configura
-#9
-tion -->
-
-#16
- <config-text
-#45
-xmlns="http://example.com/text/1.2/config"/>
-
-#22
- </filter>
- </
-#18
-get-config>
-</rpc>
-##
+++ /dev/null
-
-#43
-<rpc message-id="101" xmlns="urn:ietf:param
-#41
-s:xml:ns:netconf:base:1.0">
- <edit-confi
-#29
-g>
- <target>
- <ru
-#13
-nning/>
- <
-#4
-/tar
-#18
-get>
- <config>
-
-#41
- <top xmlns="http://example.com/schema/1
-#32
-.2/config">
- <interface>
-
-#29
- <name>Ethernet0/0</na
-#30
-me>
- <mtu>1500</mtu>
-
-#61
-</interface>
- </top>
- </config>
- </e
-#9
-dit-confi
-#9
-g>
-</rpc>
-##
+++ /dev/null
-curl http://localhost:17777/jolokia/read/org.opendaylight.controller:instanceName=fixed1,type=ConfigBean,interfaceName=testing-threadpool | jsonpp
-{
- "request": {
- "mbean": "org.opendaylight.controller:instanceName=fixed1,interfaceName=testing-threadpool,type=ConfigBean",
- "type": "read"
- },
- "status": 200,
- "timestamp": 1362416252,
- "value": {
- "ExportedInterfaces": [
- "testing-threadpool",
- "modifiable-threadpool"
- ],
- "ImplementationName": "fixed",
- "ThreadCount": 10,
- "TriggerNewInstanceCreation": false
- }
-}
\ No newline at end of file
+++ /dev/null
-$ curl 'http://localhost:17777/jolokia/exec/org.opendaylight.controller:type=ConfigRegistry/lookupConfigBeans()' | jsonpp
-{
- "request": {
- "mbean": "org.opendaylight.controller:type=ConfigRegistry",
- "operation": "lookupConfigBeans()",
- "type": "exec"
- },
- "status": 200,
- "timestamp": 1362417043,
- "value": [
- {
- "objectName": "org.opendaylight.controller:instanceName=fixed1,interfaceName=modifiable-threadpool,type=ConfigBean"
- },
- {
- "objectName": "org.opendaylight.controller:instanceName=fixed1,interfaceName=testing-threadpool,type=ConfigBean"
- }
- ]
-}
+++ /dev/null
-<rpc message-id="104"
- xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
- <commit/>
-</rpc>
\ No newline at end of file
+++ /dev/null
-<rpc message-id="102"
- xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
- <lock>
- <target>
- <candidate/>
- </target>
- </lock>
-</rpc>
\ No newline at end of file
+++ /dev/null
-<rpc message-id="101"
- xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
- <lock>
- <target>
- <running/>
- </target>
- </lock>
-</rpc>
\ No newline at end of file
+++ /dev/null
-<rpc message-id="103"
- xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
- <edit-config>
- <target>
- <candidate/>
- </target>
- <default-operation>none</default-operation>
- <test-option>test-then-set</test-option>
- <error-option>stop-on-error</error-option>
- <nc:config
- xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"
- xmlns="uri-for-my-data-model-namespace">
- <some-existing-node>
- <my-new-node nc:operation="create">
- <my-new-leaf>7</my-new-leaf>
- </my-new-node>
- </some-existing-node>
- </nc:config>
- </edit-config>
-</rpc>
\ No newline at end of file
+++ /dev/null
-<rpc message-id="105"
- xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
- <unlock>
- <target>
- <candidate/>
- </target>
- </unlock>
-</rpc>
\ No newline at end of file
+++ /dev/null
-<rpc message-id="106"
- xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
- <unlock>
- <target>
- <running/>
- </target>
- </unlock>
-</rpc>
\ No newline at end of file
+++ /dev/null
-<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
- <rpc-error>
- <error-type>rpc</error-type>
- <error-tag>missing-attribute</error-tag>
- <error-severity>error</error-severity>
- <error-info>
- <bad-attribute>message-id</bad-attribute>
- <bad-element>rpc</bad-element>
- </error-info>
- </rpc-error>
-</rpc-reply>
<version>${config.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>shutdown-api</artifactId>
+ <version>${config.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>shutdown-impl</artifactId>
+ <version>${config.version}</version>
+ </dependency>
+
<!-- Netconf -->
<dependency>
<groupId>org.opendaylight.controller</groupId>